2
* KFontInst - KDE Font Installer
4
* Copyright 2003-2007 Craig Drummond <craig@kde.org>
8
* This program is free software; you can redistribute it and/or modify
9
* it under the terms of the GNU General Public License as published by
10
* the Free Software Foundation; either version 2 of the License, or
11
* (at your option) any later version.
13
* This program is distributed in the hope that it will be useful,
14
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
* General Public License for more details.
18
* You should have received a copy of the GNU General Public License
19
* along with this program; see the file COPYING. If not, write to
20
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21
* Boston, MA 02110-1301, USA.
24
#include <QtCore/QFile>
25
#include <QtCore/QCoreApplication>
26
#include <KDE/KComponentData>
28
#include <KDE/KMimeType>
29
#include <KDE/KStandardDirs>
30
#include <KDE/KTemporaryFile>
31
#include <KDE/KTempDir>
41
#include "KfiConstants.h"
42
#include "FontInstInterface.h"
46
#include "XmlStrings.h"
50
#include "config-workspace.h"
52
#define MAX_IPC_SIZE (1024*32)
53
#define KFI_DBUG kDebug(7000) << '(' << time(NULL) << ')'
55
static const int constReconfigTimeout = 10;
59
KDE_EXPORT int kdemain(int argc, char **argv)
63
fprintf(stderr, "Usage: kio_"KFI_KIO_FONTS_PROTOCOL
64
" protocol domain-socket1 domain-socket2\n");
68
KLocale::setMainCatalog(KFI_CATALOGUE);
70
KComponentData componentData("kio_"KFI_KIO_FONTS_PROTOCOL);
71
KFI::CKioFonts slave(argv[2], argv[3]);
72
QCoreApplication app(argc, argv);
84
inline bool isSysFolder(const QString &folder)
86
return i18n(KFI_KIO_FONTS_SYS)==folder || KFI_KIO_FONTS_SYS==folder;
89
inline bool isUserFolder(const QString &folder)
91
return i18n(KFI_KIO_FONTS_USER)==folder || KFI_KIO_FONTS_USER==folder;
94
static CKioFonts::EFolder getFolder(const QStringList &list)
98
QString folder=list[0];
100
if(isSysFolder(folder))
101
return CKioFonts::FOLDER_SYS;
102
else if(isUserFolder(folder))
103
return CKioFonts::FOLDER_USER;
104
return CKioFonts::FOLDER_UNKNOWN;
107
return CKioFonts::FOLDER_ROOT;
110
static int getSize(const QString &file)
112
KDE_struct_stat buff;
113
QByteArray f(QFile::encodeName(file));
115
if(-1!=KDE_lstat(f.constData(), &buff))
117
if (S_ISLNK(buff.st_mode))
120
int n=readlink(f.constData(), buffer2, 1000);
124
if(-1==KDE_stat(f.constData(), &buff))
133
static bool writeAll(int fd, const char *buf, size_t len)
137
ssize_t written=write(fd, buf, len);
138
if (written<0 && EINTR!=errno)
146
static bool isScalable(const QString &str)
148
return Misc::checkExt(str, "ttf") || Misc::checkExt(str, "otf") || Misc::checkExt(str, "ttc") ||
149
Misc::checkExt(str, "pfa") || Misc::checkExt(str, "pfb");
152
static const char * const constExtensions[]=
153
{".ttf", KFI_FONTS_PACKAGE, ".otf", ".pfa", ".pfb", ".ttc",
154
".pcf", ".pcf.gz", ".bdf", ".bdf.gz", NULL };
156
static QString removeKnownExtension(const KUrl &url)
158
QString fname(url.fileName());
161
for(int i=0; constExtensions[i]; ++i)
162
if(-1!=(pos=fname.lastIndexOf(QString::fromLatin1(constExtensions[i]), -1, Qt::CaseInsensitive)))
163
return fname.left(pos);
168
CKioFonts::CKioFonts(const QByteArray &pool, const QByteArray &app)
169
: KIO::SlaveBase(KFI_KIO_FONTS_PROTOCOL, pool, app)
170
, itsInterface(new FontInstInterface())
176
CKioFonts::~CKioFonts()
183
void CKioFonts::listDir(const KUrl &url)
185
KFI_DBUG << url.prettyUrl();
187
QStringList pathList(url.path(KUrl::RemoveTrailingSlash).split('/', QString::SkipEmptyParts));
188
EFolder folder=Misc::root() ? FOLDER_SYS : getFolder(pathList);
195
KFI_DBUG << "List root folder";
198
createUDSEntry(entry, FOLDER_SYS);
199
listEntry(entry, false);
200
createUDSEntry(entry, FOLDER_USER);
201
listEntry(entry, false);
205
size=listFolder(entry, folder);
211
if(FOLDER_UNKNOWN!=folder)
213
listEntry(size ? entry : KIO::UDSEntry(), true);
217
error(KIO::ERR_DOES_NOT_EXIST, url.prettyUrl());
220
void CKioFonts::put(const KUrl &url, int /*permissions*/, KIO::JobFlags /*flags*/)
222
KFI_DBUG << url.prettyUrl();
223
QStringList pathList(url.path(KUrl::RemoveTrailingSlash).split('/', QString::SkipEmptyParts));
224
EFolder folder(getFolder(pathList));
226
if(!Misc::root() && FOLDER_ROOT==folder)
227
error(KIO::ERR_SLAVE_DEFINED,
228
i18n("Can only install fonts to either \"%1\" or \"%2\".",
229
i18n(KFI_KIO_FONTS_USER), i18n(KFI_KIO_FONTS_SYS)));
230
else if(Misc::isPackage(url.fileName()))
231
error(KIO::ERR_SLAVE_DEFINED, i18n("You cannot install a fonts package directly.\n"
232
"Please extract %1, and install the components individually.",
238
itsTempDir=new KTempDir(KStandardDirs::locateLocal("tmp",
239
QString::fromLatin1("kio_fonts_")+QString::number(getpid())));
240
itsTempDir->setAutoRemove(true);
243
QString tempFile(itsTempDir->name()+QChar('/')+url.fileName());
244
QFile dest(tempFile);
246
if (dest.open(QIODevice::WriteOnly))
249
// Loop until we got 0 (end of data)
254
dataReq(); // Request for data
255
result = readData(buffer);
256
if(result > 0 && !writeAll(dest.handle(), buffer.constData(), buffer.size()))
258
if(ENOSPC==errno) // disk full
260
error(KIO::ERR_DISK_FULL, dest.fileName());
261
result = -2; // means: remove dest file
265
error(KIO::ERR_COULD_NOT_WRITE, dest.fileName());
278
handleResp(itsInterface->install(tempFile, Misc::root() || FOLDER_SYS==folder),
279
url.fileName(), tempFile);
280
QFile::remove(tempFile);
283
error(EACCES==errno ? KIO::ERR_WRITE_ACCESS_DENIED : KIO::ERR_CANNOT_OPEN_FOR_WRITING, dest.fileName());
287
void CKioFonts::get(const KUrl &url)
289
KFI_DBUG << url.prettyUrl();
290
QStringList pathList(url.path(KUrl::RemoveTrailingSlash).split('/', QString::SkipEmptyParts));
291
EFolder folder(getFolder(pathList));
292
Family family(getFont(url, folder));
294
if(!family.name().isEmpty() && 1==family.styles().count())
296
StyleCont::ConstIterator style(family.styles().begin());
297
FileCont::ConstIterator it((*style).files().begin()),
298
end((*style).files().end());
301
// The thumbnail job always donwloads non-local files to /tmp/... and passes this file name to
302
// the thumbnail creator. However, in the case of fonts which are split among many files, this
303
// wont work. Therefore, when the thumbnail code asks for the font to donwload, just return
304
// the family and style info for enabled fonts, and the filename for disabled fonts. This way
305
// the font-thumbnail creator can read this and just ask Xft/fontconfig for the font data.
306
if("1"==metaData("thumbnail"))
309
QTextStream stream(&array, QIODevice::WriteOnly);
311
emit mimeType("text/plain");
315
for(; it!=end && hidden; ++it)
316
if(!Misc::isHidden(Misc::getFile((*it).path())))
322
// OK, its a disabled font - if possible try to return the location of the font file
325
it=(*style).files().begin();
326
end=(*style).files().end();
327
for(; it!=end && hidden; ++it)
328
if(isScalable((*it).path()))
330
KFI_DBUG << "hasMetaData(\"thumbnail\"), so return FILE: "
331
<< (*it).path() << " / " << (*it).index();
332
stream << KFI_PATH_KEY << (*it).path() << endl
333
<< KFI_FACE_KEY << (*it).index() << endl;
340
KFI_DBUG << "hasMetaData(\"thumbnail\"), so return Url: " << url;
341
stream << url.prettyUrl();
346
KFI_DBUG << "hasMetaData(\"thumbnail\"), so return DETAILS: " << family.name() << " / "
349
stream << KFI_NAME_KEY << family.name() << endl
350
<< KFI_STYLE_KEY << (*style).value() << endl;
353
totalSize(array.size());
355
processedSize(array.size());
357
processedSize(array.size());
359
KFI_DBUG << "Finished thumbnail...";
365
KDE_struct_stat buff;
372
files.insert((*it).path());
374
Misc::getAssociatedFiles((*it).path(), assoc);
376
QStringList::ConstIterator ait(assoc.constBegin()),
377
aend(assoc.constEnd());
379
for(; ait!=aend; ++ait)
384
realPath=(*files.begin());
385
else // Font is made up of multiple files - so create .zip of them all!
387
KTemporaryFile tmpFile;
391
KZip zip(tmpFile.fileName());
393
tmpFile.setAutoRemove(false);
394
realPath=tmpFile.fileName();
396
if(zip.open(QIODevice::WriteOnly))
398
QMap<QString, QString> map=Misc::getFontFileMap(files);
399
QMap<QString, QString>::ConstIterator it(map.constBegin()),
403
zip.addLocalFile(it.value(), it.key());
411
QByteArray realPathC(QFile::encodeName(realPath));
412
KFI_DBUG << "real: " << realPathC;
414
if (-2==KDE_stat(realPathC.constData(), &buff))
415
error(EACCES==errno ? KIO::ERR_ACCESS_DENIED : KIO::ERR_DOES_NOT_EXIST, url.prettyUrl());
416
else if (S_ISDIR(buff.st_mode))
417
error(KIO::ERR_IS_DIRECTORY, url.prettyUrl());
418
else if (!S_ISREG(buff.st_mode))
419
error(KIO::ERR_CANNOT_OPEN_FOR_READING, url.prettyUrl());
422
int fd = KDE_open(realPathC.constData(), O_RDONLY);
425
error(KIO::ERR_CANNOT_OPEN_FOR_READING, url.prettyUrl());
428
// Determine the mimetype of the file to be retrieved, and emit it.
429
// This is mandatory in all slaves (for KRun/BrowserRun to work).
430
emit mimeType(KMimeType::findByPath(realPathC, buff.st_mode)->name());
432
totalSize(buff.st_size);
434
KIO::filesize_t processed=0;
435
char buffer[MAX_IPC_SIZE];
440
int n=::read(fd, buffer, MAX_IPC_SIZE);
447
error(KIO::ERR_COULD_NOT_READ, url.prettyUrl());
456
array=array.fromRawData(buffer, n);
461
processedSize(processed);
466
processedSize(buff.st_size);
474
error(KIO::ERR_COULD_NOT_READ, url.prettyUrl());
477
void CKioFonts::copy(const KUrl &, const KUrl &, int, KIO::JobFlags)
479
error(KIO::ERR_SLAVE_DEFINED, i18n("Cannot copy fonts"));
482
void CKioFonts::rename(const KUrl &, const KUrl &, KIO::JobFlags)
484
error(KIO::ERR_SLAVE_DEFINED, i18n("Cannot move fonts"));
487
void CKioFonts::del(const KUrl &url, bool isFile)
489
KFI_DBUG << url.prettyUrl();
490
QStringList pathList(url.path(KUrl::RemoveTrailingSlash).split('/', QString::SkipEmptyParts));
491
EFolder folder(getFolder(pathList));
492
QString name(removeKnownExtension(url));
495
error(KIO::ERR_SLAVE_DEFINED, i18n("Only fonts may be deleted."));
496
else if(!Misc::root() && FOLDER_ROOT==folder)
497
error(KIO::ERR_SLAVE_DEFINED,
498
i18n("Can only remove fonts from either \"%1\" or \"%2\".",
499
i18n(KFI_KIO_FONTS_USER), i18n(KFI_KIO_FONTS_SYS)));
500
else if(!name.isEmpty())
501
handleResp(itsInterface->uninstall(name, Misc::root() || FOLDER_SYS==folder), name);
503
error(KIO::ERR_DOES_NOT_EXIST, url.prettyUrl());
506
void CKioFonts::stat(const KUrl &url)
508
KFI_DBUG << url.prettyUrl();
510
QStringList pathList(url.path(KUrl::RemoveTrailingSlash).split('/', QString::SkipEmptyParts));
511
EFolder folder=getFolder(pathList);
515
switch(pathList.count())
518
createUDSEntry(entry, FOLDER_ROOT);
522
ok=createStatEntry(entry, url, FOLDER_SYS);
523
else if(FOLDER_SYS==folder || FOLDER_USER==folder)
524
createUDSEntry(entry, folder);
527
error(KIO::ERR_SLAVE_DEFINED,
528
i18n("Please specify \"%1\" or \"%2\".",
529
i18n(KFI_KIO_FONTS_USER), i18n(KFI_KIO_FONTS_SYS)));
534
ok=createStatEntry(entry, url, folder);
544
error(KIO::ERR_DOES_NOT_EXIST, url.prettyUrl());
549
void CKioFonts::special(const QByteArray &a)
552
error(KIO::ERR_UNSUPPORTED_ACTION, i18n("No special methods supported."));
555
setTimeoutSpecialCommand(-1);
556
itsInterface->reconfigure();
560
int CKioFonts::listFolder(KIO::UDSEntry &entry, EFolder folder)
565
KFI::Families families(itsInterface->list(FOLDER_SYS==folder));
566
FamilyCont::ConstIterator family(families.items.begin()),
567
end(families.items.end());
569
KFI_DBUG << "Num families:" << families.items.count();
571
for(; family!=end; ++family)
573
StyleCont::ConstIterator styleIt((*family).styles().begin()),
574
styleEnd((*family).styles().end());
576
styleCount+=(*family).styles().count();
577
for(; styleIt!=styleEnd; ++styleIt)
579
createUDSEntry(entry, folder, *family, *styleIt);
580
listEntry(entry, false);
584
totalSize(styleCount);
588
QString CKioFonts::getUserName(uid_t uid)
590
if (!itsUserCache.contains(uid))
592
struct passwd *user = getpwuid(uid);
594
itsUserCache.insert(uid, QString::fromLatin1(user->pw_name));
596
return QString::number(uid);
598
return itsUserCache[uid];
601
QString CKioFonts::getGroupName(gid_t gid)
603
if (!itsGroupCache.contains(gid))
605
struct group *grp = getgrgid(gid);
607
itsGroupCache.insert(gid, QString::fromLatin1(grp->gr_name));
609
return QString::number(gid);
611
return itsGroupCache[gid];
614
bool CKioFonts::createStatEntry(KIO::UDSEntry &entry, const KUrl &url, EFolder folder)
616
Family fam(getFont(url, folder));
618
if(!fam.name().isEmpty() && 1==fam.styles().count())
620
createUDSEntry(entry, folder, fam, *fam.styles().begin());
627
void CKioFonts::createUDSEntry(KIO::UDSEntry &entry, EFolder folder)
629
KFI_DBUG << QString(FOLDER_SYS==folder ? i18n(KFI_KIO_FONTS_SYS) : i18n(KFI_KIO_FONTS_USER));
631
entry.insert(KIO::UDSEntry::UDS_NAME, FOLDER_ROOT==folder || Misc::root()
634
? i18n(KFI_KIO_FONTS_SYS)
635
: i18n(KFI_KIO_FONTS_USER));
636
entry.insert(KIO::UDSEntry::UDS_ACCESS, !Misc::root() && FOLDER_SYS==folder ? 0444 : 0744);
637
entry.insert(KIO::UDSEntry::UDS_USER, Misc::root() || FOLDER_SYS==folder
638
? QString::fromLatin1("root")
639
: getUserName(getuid()));
640
entry.insert(KIO::UDSEntry::UDS_GROUP, Misc::root() || FOLDER_SYS==folder
641
? QString::fromLatin1("root")
642
: getGroupName(getgid()));
643
entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
644
entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("inode/directory"));
647
bool CKioFonts::createUDSEntry(KIO::UDSEntry &entry, EFolder folder, const Family &family, const Style &style)
650
QString name(FC::createName(family.name(), style.value()));
651
FileCont::ConstIterator file(style.files().begin()),
652
fileEnd(style.files().end());
655
haveExtraFiles=false;
659
for(; file!=fileEnd; ++file)
661
size+=getSize((*file).path());
662
// TODO: Make scalable a property of the file?
663
// Then isScalable() is not needed!!!
664
if(isScalable((*file).path()))
665
files.prepend(*file);
669
if(hidden && !Misc::isHidden(Misc::getFile((*file).path())))
673
Misc::getAssociatedFiles((*file).path(), assoc);
675
QStringList::ConstIterator oit(assoc.constBegin()),
676
oend(assoc.constEnd());
678
if(!haveExtraFiles && !assoc.isEmpty())
681
for(; oit!=oend; ++oit)
687
entry.insert(KIO::UDSEntry::UDS_NAME, name);
688
entry.insert(KIO::UDSEntry::UDS_SIZE, size);
689
entry.insert(UDS_EXTRA_FC_STYLE, style.value());
691
QList<File>::ConstIterator it(files.constBegin()),
692
end(files.constEnd());
696
QByteArray cPath(QFile::encodeName((*it).path()));
697
KDE_struct_stat buff;
699
if(-1!=KDE_lstat(cPath, &buff))
701
QString fileName(Misc::getFile((*it).path())),
703
int dotPos(fileName.lastIndexOf('.'));
704
QString extension(-1==dotPos ? QString() : fileName.mid(dotPos));
706
if(QString::fromLatin1(".gz")==extension)
708
dotPos=fileName.lastIndexOf('.', dotPos-1);
709
extension=-1==dotPos ? QString() : fileName.mid(dotPos);
712
if(QString::fromLatin1(".ttf")==extension || QString::fromLatin1(".ttc")==extension)
713
mt="application/x-font-ttf";
714
else if(QString::fromLatin1(".otf")==extension)
715
mt="application/x-font-otf";
716
else if(QString::fromLatin1(".pfa")==extension || QString::fromLatin1(".pfb")==extension)
717
mt="application/x-font-type1";
718
else if(QString::fromLatin1(".pcf.gz")==extension || QString::fromLatin1(".pcf")==extension)
719
mt="application/x-font-pcf";
720
else if(QString::fromLatin1(".bdf.gz")==extension || QString::fromLatin1(".bdf")==extension)
721
mt="application/x-font-bdf";
724
// File extension check failed, use kmimetype to read contents...
725
KMimeType::Ptr mime=KMimeType::findByPath((*it).path());
726
QStringList patterns=mime->patterns();
728
if(patterns.size()>0)
729
extension=(*patterns.begin()).remove("*");
732
entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, buff.st_mode&S_IFMT);
733
entry.insert(KIO::UDSEntry::UDS_ACCESS, buff.st_mode&07777);
734
entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, buff.st_mtime);
735
entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, buff.st_atime);
736
entry.insert(KIO::UDSEntry::UDS_USER, getUserName(buff.st_uid));
737
entry.insert(KIO::UDSEntry::UDS_GROUP, getGroupName(buff.st_gid));
738
entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, mt);
742
entry.insert(KIO::UDSEntry::UDS_HIDDEN, 1);
743
entry.insert(UDS_EXTRA_FILE_NAME, (*it).path());
744
entry.insert(UDS_EXTRA_FILE_FACE, (*it).path());
747
QString path(QString::fromLatin1("/"));
751
path+=FOLDER_SYS==folder ? i18n(KFI_KIO_FONTS_SYS) : i18n(KFI_KIO_FONTS_USER);
752
path+=QString::fromLatin1("/");
756
if(files.count()>1 || haveExtraFiles)
757
path+=QString::fromLatin1(KFI_FONTS_PACKAGE);
761
KUrl url(KUrl::fromPath(path));
763
url.setProtocol(KFI_KIO_FONTS_PROTOCOL);
764
entry.insert(KIO::UDSEntry::UDS_URL, url.url());
772
Family CKioFonts::getFont(const KUrl &url, EFolder folder)
774
QString name(removeKnownExtension(url));
776
KFI_DBUG << url << name;
778
return itsInterface->stat(name, FOLDER_SYS==folder);
781
void CKioFonts::handleResp(int resp, const QString &file, const QString &tempFile)
785
case FontInst::STATUS_NO_SYS_CONNECTION:
786
error(KIO::ERR_SLAVE_DEFINED, i18n("Failed to start the system daemon"));
788
case FontInst::STATUS_SERVICE_DIED:
789
error(KIO::ERR_SLAVE_DEFINED, i18n("Backend died"));
791
case FontInst::STATUS_BITMAPS_DISABLED:
792
error(KIO::ERR_SLAVE_DEFINED,
793
i18n("%1 is a bitmap font, and these have been disabled on your system.", file));
795
case FontInst::STATUS_ALREADY_INSTALLED:
796
error(KIO::ERR_SLAVE_DEFINED,
797
i18n("%1 contains the font <b>%2</b>, which is already installed on your system.", file,
798
FC::getName(tempFile)));
800
case FontInst::STATUS_NOT_FONT_FILE:
801
error(KIO::ERR_SLAVE_DEFINED, i18n("%1 is not a font.", file));
803
case FontInst::STATUS_PARTIAL_DELETE:
804
error(KIO::ERR_SLAVE_DEFINED, i18n("Could not remove all files associated with %1", file));
806
case FontInst::STATUS_OK:
813
if(FontInst::STATUS_OK==resp)
814
setTimeoutSpecialCommand(constReconfigTimeout);