Pass ImlibImageLists over to the view
[qcomicbook-oblomov] / src / imgarchivesink.cpp
1 /*
2  * This file is a part of QComicBook.
3  *
4  * Copyright (C) 2005-2006 Pawel Stolowski <yogin@linux.bydg.org>
5  *
6  * QComicBook is free software; you can redestribute it and/or modify it
7  * under terms of GNU General Public License by Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY. See GPL for more details.
11  */
12
13 #include "imgarchivesink.h"
14 #include "miscutil.h"
15 #include <qimage.h>
16 #include <qstringlist.h>
17 #include <qprocess.h>
18 #include <qfileinfo.h>
19 #include <qfile.h>
20 #include <qregexp.h>
21 #include <qapplication.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <unistd.h>
27 #include <iostream>
28
29 using namespace QComicBook;
30 using Utility::which;
31
32 QString ImgArchiveSink::openext;
33 QString ImgArchiveSink::saveext;
34 int ImgArchiveSink::suppopen;
35 int ImgArchiveSink::suppsave;
36
37 QValueList<ImgArchiveSink::ArchiveTypeInfo> ImgArchiveSink::archinfo;
38
39 ImgArchiveSink::ImgArchiveSink(): ImgDirSink()
40 {
41         init();
42 }
43
44 ImgArchiveSink::ImgArchiveSink(const QString &path): ImgDirSink()
45 {
46         init();
47         open(path);
48 }
49
50 ImgArchiveSink::ImgArchiveSink(const ImgDirSink &sink): ImgDirSink(sink)
51 {
52         init();
53 }
54
55 ImgArchiveSink::~ImgArchiveSink()
56 {
57         ImgArchiveSink::close();
58 }
59
60 void ImgArchiveSink::init()
61 {
62         pinf = new QProcess(this);
63         pext = new QProcess(this);
64         connect(pinf, SIGNAL(readyReadStdout()), this, SLOT(infoStdoutReady()));
65         connect(pinf, SIGNAL(processExited()), this, SLOT(infoExited()));
66         connect(pext, SIGNAL(readyReadStdout()), this, SLOT(extractStdoutReady()));
67         connect(pext, SIGNAL(processExited()), this, SLOT(extractExited()));
68 }
69
70 void ImgArchiveSink::doCleanup()
71 {
72         if (!tmppath.isEmpty())
73         {
74                 QDir dir(tmppath);
75                 //
76                 // remove temporary files and dirs
77                 for (QStringList::const_iterator it = archfiles.begin(); it != archfiles.end(); ++it)
78                         dir.remove(*it);
79                 for (QStringList::const_iterator it = archdirs.begin(); it != archdirs.end(); ++it)
80                         dir.rmdir(*it);
81                 dir.rmdir(tmppath);
82         }
83 }
84
85 ImgArchiveSink::ArchiveType ImgArchiveSink::archiveType(const QString &filename)
86 {
87         static const struct {
88                 int offset; //the first byte to compare
89                 int len; //number of bytes in pattern
90                 const char *ptrn; //pattern
91                 ArchiveType type; //archive type
92         } magic[] = {
93                 {0, 4, "\x52\x61\x72\x21", RAR_ARCHIVE},
94                 {0, 4, "\x50\x4b\x03\x04", ZIP_ARCHIVE},
95                 {8, 4, "\x2a\x41\x43\x45", ACE_ARCHIVE}
96         };
97
98         QFile file(filename);
99         if (file.open(IO_ReadOnly))
100         {
101                 for (int i=0; i<3; i++)
102                 {
103                         int j;
104                         if (!file.at(magic[i].offset))
105                                 continue;
106                         for (j=0; j<magic[i].len; j++)
107                         {
108                                 int c;
109                                 if ((c = file.getch()) < 0)
110                                         break;
111                                 if (c != magic[i].ptrn[j])
112                                         break;
113                         }
114                         if (j == magic[i].len)
115                         {
116                                 file.close();
117                                 return magic[i].type;
118                         }
119                 }
120                 file.close();
121         }
122
123         //
124         // try to match filename extension
125         for (QValueList<ArchiveTypeInfo>::const_iterator it = archinfo.begin(); it!=archinfo.end(); it++)
126         {
127                 const ArchiveTypeInfo &inf = *it;
128                 for (QStringList::const_iterator sit = inf.extensions.begin(); sit!=inf.extensions.end(); sit++)
129                 {
130                         if (filename.endsWith(*sit, false))
131                                 return inf.type;
132                 }
133         }
134
135         return UNKNOWN_ARCHIVE;
136 }
137
138 int ImgArchiveSink::extract(const QString &filename, const QString &destdir)
139 {
140         archivetype = archiveType(filename);
141         filesnum = 0;
142
143         if (archivetype == UNKNOWN_ARCHIVE)
144                 return SINKERR_UNKNOWNFILE;
145         if (!supportsOpen(archivetype))
146                 return SINKERR_NOTSUPPORTED;    
147
148         //
149         // match archive type, set subprocess extract and list options
150         for (QValueList<ArchiveTypeInfo>::const_iterator it = archinfo.begin(); it!=archinfo.end(); it++)
151         {
152                 const ArchiveTypeInfo &inf = *it;
153                 if (archivetype == inf.type)
154                 {
155                         pext->setArguments(inf.extractopts);
156                         pinf->setArguments(inf.listopts);
157                         break;
158                 }
159         }
160         
161         pext->addArgument(filename);
162         pinf->addArgument(filename);
163         pext->setWorkingDirectory(destdir);
164
165         //
166         // extract archive file list first
167         if (!pinf->start())
168                 return SINKERR_OTHER;
169         return 0;
170 }
171
172 int ImgArchiveSink::open(const QString &path)
173 {
174         QFileInfo info(path);
175         archivepath = path;
176         archivename = info.fileName();
177         if (!info.exists())
178         {
179                 emit sinkError(SINKERR_NOTFOUND);
180                 return SINKERR_NOTFOUND;
181         }
182         if (info.isFile())
183         {
184                 if (info.isReadable())
185                 {
186                         tmppath = makeTempDir();
187                         int status = extract(path, tmppath);
188                         if (status != 0)
189                         {
190                                 emit sinkError(status);
191                                 close();
192                         }
193                         return status;
194                 }
195                 else
196                 {
197                         emit sinkError(SINKERR_ACCESS);
198                         return SINKERR_ACCESS;
199                 }
200         }
201         emit sinkError(SINKERR_NOTFILE);
202         return SINKERR_NOTFILE;
203 }
204
205 void ImgArchiveSink::close()
206 {
207         ImgDirSink::close();
208         doCleanup();
209         archivename = QString::null;
210 }
211
212 QString ImgArchiveSink::getName(int maxlen)
213 {
214         if (archivename.length() < maxlen)
215                 return archivename;
216         QString tmpname = archivename.left(maxlen-3) + "...";
217         return tmpname;
218 }
219
220 QString ImgArchiveSink::getFullName() const
221 {
222         return archivepath;
223 }
224
225 void ImgArchiveSink::infoExited()
226 {
227         extcnt = 0;
228         if (!pext->start())
229                 emit sinkError(SINKERR_OTHER);
230 }
231
232 void ImgArchiveSink::extractExited()
233 {
234         //
235         // open temporary directory using ImgDirSink::open()
236         ImgDirSink::blockSignals(true);
237         int status = ImgDirSink::open(tmppath);
238         ImgDirSink::blockSignals(false);
239
240         archfiles = ImgDirSink::getAllfiles();
241         archdirs = ImgDirSink::getAlldirs();
242         setComicBookName(archivename);
243
244         if (!pext->normalExit())
245         {
246                 emit sinkError(SINKERR_ARCHEXIT);
247                 close();
248         }
249         else if (status != 0)
250         {
251                 emit sinkError(status);
252                 close();
253         }
254         else
255         {
256                 emit progress(1, 1);
257                 //
258                 // fix permissions of files; this is needed for ace archives as unace
259                 // is buggy and sets empty permissions.
260                 for (QStringList::const_iterator it = archfiles.begin(); it!=archfiles.end(); ++it)
261                 {
262                         QFileInfo finfo(*it);
263                         if (!finfo.isReadable())
264                                 chmod(*it, S_IRUSR|S_IWUSR);
265                 }
266                 emit sinkReady(archivepath);
267         }
268 }
269
270 void ImgArchiveSink::infoStdoutReady()
271 {
272         QByteArray b = pinf->readStdout();
273         for (int i=0; i<b.size(); i++)
274                 if (b[i] == '\n')
275                         ++filesnum;
276 }
277
278 void ImgArchiveSink::extractStdoutReady()
279 {
280         QByteArray b = pext->readStdout();
281         for (int i=0; i<b.size(); i++)
282                 if (b[i] == '\n' && extcnt < filesnum)
283                         ++extcnt;
284         emit progress(extcnt, filesnum);
285         qApp->processEvents();
286 }
287
288 void ImgArchiveSink::autoconfRAR()
289 {
290         ArchiveTypeInfo inf;
291         inf.type = RAR_ARCHIVE;
292         inf.name = "rar";
293         inf.extensions.append(".rar");
294         inf.extensions.append(".cbr");
295         inf.reading = inf.writing = false;
296
297         if (which("rar") != QString::null)
298         {
299                 inf.extractopts.append("rar");
300                 inf.extractopts.append("x");
301                 inf.listopts.append("rar");
302                 inf.listopts.append("lb");
303                 inf.reading = inf.writing = true;
304                 inf.compressopts.append("rar");
305                 inf.compressopts.append("a");
306         }
307         else if (which("unrar") != QString::null)
308         {
309                 FILE *f;
310                 bool nonfree_unrar = false;
311                 inf.extractopts.append("unrar");
312                 inf.listopts.append("unrar");
313                 //
314                 // now determine which unrar it is - free or non-free
315                 if ((f = popen("unrar", "r")) != NULL)
316                 {
317                         QRegExp regexp("^UNRAR.+freeware");
318                         for (QTextIStream s(f); !s.atEnd(); )
319                         {
320                                 if (regexp.search(s.readLine()) >= 0)
321                                 {
322                                         nonfree_unrar = true;
323                                         break;
324                                 }
325                         }
326                         pclose(f);
327                         if (nonfree_unrar)
328                         {
329                                 inf.extractopts.append("x");
330                                 inf.listopts.append("lb");
331                         }
332                         else
333                         {
334                                 inf.extractopts.append("-x");
335                                 inf.listopts.append("-t");
336                         }
337                         inf.reading = true;
338                 }
339         }
340         else if (which("unrar-free") != QString::null) //some distros rename free unrar like this
341         {
342                 inf.extractopts.append("unrar-free");
343                 inf.listopts.append("unrar-free");
344                 inf.extractopts.append("-x");
345                 inf.listopts.append("-t");
346                 inf.reading = true;
347         }
348         archinfo.append(inf);
349 }
350
351 void ImgArchiveSink::autoconfZIP()
352 {
353         ArchiveTypeInfo inf;
354         inf.type = ZIP_ARCHIVE;
355         inf.name = "zip";
356         inf.extensions.append(".zip");
357         inf.extensions.append(".cbz");
358         inf.reading = inf.writing = false;
359         if (which("unzip") != QString::null)
360         {
361                 inf.extractopts.append("unzip");
362                 inf.listopts.append("unzip");
363                 inf.listopts.append("-l");
364                 inf.reading = true;
365         }
366         if (which("zip") != QString::null)
367         {
368                 inf.writing = true;
369                 inf.compressopts.append("zip");
370         }
371         archinfo.append(inf);
372 }
373
374 void ImgArchiveSink::autoconfACE()
375 {
376         ArchiveTypeInfo inf;
377         inf.type = ACE_ARCHIVE;
378         inf.name = "ace";
379         inf.extensions.append(".ace");
380         inf.extensions.append(".cba");
381         inf.reading = inf.writing = false;
382         if (which("unace") != QString::null)
383         {
384                 inf.extractopts.append("unace");
385                 inf.extractopts.append("x");
386                 inf.extractopts.append("-y");
387                 inf.extractopts.append("-c-");
388                 inf.listopts.append("unace");
389                 inf.listopts.append("l");
390                 inf.listopts.append("-y");
391                 inf.listopts.append("-c-");
392                 inf.reading = true;
393         }
394         archinfo.append(inf);
395 }
396
397 void ImgArchiveSink::autoconfTARGZ()
398 {
399         ArchiveTypeInfo inf;
400         inf.type = TARGZ_ARCHIVE;
401         inf.name = "tar.gz";
402         inf.extensions.append(".tar.gz");
403         inf.extensions.append(".tgz");
404         inf.extensions.append(".cbg");
405         inf.reading = inf.writing = false;
406         if (which("tar") != QString::null)
407         {
408                 inf.extractopts.append("tar");
409                 inf.extractopts.append("-xvzf");
410                 inf.listopts.append("tar");
411                 inf.listopts.append("-tzf");
412                 inf.reading = inf.writing = true;
413                 inf.compressopts.append("tar");
414                 inf.compressopts.append("-chzvf");
415         }
416         archinfo.append(inf);
417 }
418
419 void ImgArchiveSink::autoconfTARBZ2()
420 {
421         ArchiveTypeInfo inf;
422         inf.type = TARBZ2_ARCHIVE;
423         inf.name = "tar.bz2";
424         inf.extensions.append(".tar.bz2");
425         inf.extensions.append(".cbb");
426         if (which("tar") != QString::null)
427         {
428                 inf.extractopts.append("tar");
429                 inf.extractopts.append("-xjvf");
430                 inf.listopts.append("tar");
431                 inf.listopts.append("-tjf");
432                 inf.reading = inf.writing = true;
433                 inf.compressopts.append("tar");
434                 inf.compressopts.append("-chjvf");
435         }
436         archinfo.append(inf);
437 }
438
439 void ImgArchiveSink::autoconfTAR()
440 {
441         ArchiveTypeInfo inf;
442         inf.type = TAR_ARCHIVE;
443         inf.name = "tar";
444         inf.extensions.append(".tar");
445         inf.extensions.append(".cbt");
446         if (which("tar") != QString::null)
447         {
448                 inf.extractopts.append("tar");
449                 inf.extractopts.append("-xvf");
450                 inf.listopts.append("tar");
451                 inf.listopts.append("-tf");
452                 inf.reading = inf.writing = true;
453                 inf.compressopts.append("tar");
454                 inf.compressopts.append("-chvf");
455         }
456         archinfo.append(inf);
457 }
458
459 void ImgArchiveSink::autoconf7Z()
460 {
461         ArchiveTypeInfo inf;
462         inf.type = SEVENZIP_ARCHIVE;
463         inf.name = "7-zip";
464         inf.extensions.append(".7z");
465         inf.extensions.append(".cb7");
466         if (which("7z") != QString::null)
467         {
468                 inf.extractopts.append("7z");
469                 inf.extractopts.append("x");
470                 inf.extractopts.append("-y");
471                 inf.listopts.append("7z");
472                 inf.listopts.append("l");
473                 inf.listopts.append("-y");
474                 inf.reading = true;
475         } else if (which("7za") != QString::null)
476         {
477                 inf.extractopts.append("7za");
478                 inf.extractopts.append("x");
479                 inf.extractopts.append("-y");
480                 inf.listopts.append("7za");
481                 inf.listopts.append("l");
482                 inf.listopts.append("-y");
483                 inf.reading = true;
484         }
485         archinfo.append(inf);
486 }
487
488 void ImgArchiveSink::autoconfArchivers()
489 {
490         autoconfRAR();
491         autoconfZIP();
492         autoconfACE();
493         autoconfTARGZ();
494         autoconfTARBZ2();
495         autoconfTAR();
496         autoconf7Z();
497         
498         openext = saveext = QString::null;      
499
500         for (QValueList<ArchiveTypeInfo>::const_iterator it = archinfo.begin(); it!=archinfo.end(); it++)
501         {
502                 const ArchiveTypeInfo &inf = *it;
503                 if (inf.reading)
504                         suppopen |= inf.type;
505                 if (inf.writing)
506                         suppsave |= inf.type;
507                 for (QStringList::const_iterator sit = inf.extensions.begin(); sit!=inf.extensions.end(); sit++)
508                 {
509                         if (inf.reading)
510                         {
511                                 if (openext != QString::null)
512                                         openext += " ";
513                                 openext += "*" + *sit;
514                         }
515                         if (inf.writing)
516                         {
517                                 if (saveext != QString::null)
518                                         saveext += " ";
519                                 saveext += "*" + *sit;
520                         }
521                 }
522         }
523 }
524
525 QString ImgArchiveSink::makeTempDir()
526 {
527         char tmpd[19];
528         strcpy(tmpd, "/tmp/qcomic-XXXXXX");
529         mkdtemp(tmpd);
530         return QString(tmpd);
531 }
532
533 QString ImgArchiveSink::supportedOpenExtensions()
534 {
535         return openext;
536 }
537
538 QString ImgArchiveSink::supportedSaveExtensions()
539 {
540         return saveext;
541 }
542
543 int ImgArchiveSink::supportedArchives()
544 {
545         return suppopen;
546 }
547
548 QValueList<ImgArchiveSink::ArchiveTypeInfo> ImgArchiveSink::supportedArchivesInfo()
549 {
550         return archinfo;
551 }
552
553 bool ImgArchiveSink::supportsOpen(ArchiveType t)
554 {
555         return (suppopen & t) > 0;
556 }
557
558 bool ImgArchiveSink::supportsSave(ArchiveType t)
559 {
560         return (suppsave & t) > 0;
561 }
562
563 bool ImgArchiveSink::supportsNext() const
564 {
565         return true;
566 }
567
568 QString ImgArchiveSink::getNext() const
569 {
570         QFileInfo finfo(getFullName());
571         QDir dir(finfo.dirPath(true)); //get the full path of current cb
572         QStringList files = dir.entryList(ImgArchiveSink::supportedOpenExtensions(), QDir::Files|QDir::Readable, QDir::Name);
573         QStringList::iterator it = files.find(finfo.fileName()); //find current cb
574         if (it != files.end())
575                 if (++it != files.end()) //get next file name
576                         return dir.filePath(*it, true);
577         return QString::null;
578 }
579
580 QString ImgArchiveSink::getPrevious() const
581 {
582         QFileInfo finfo(getFullName());
583         QDir dir(finfo.dirPath(true)); //get the full path of current cb
584         QStringList files = dir.entryList(ImgArchiveSink::supportedOpenExtensions(), QDir::Files|QDir::Readable, QDir::Name);
585         QStringList::iterator it = files.find(finfo.fileName()); //find current cb
586         if (--it != files.begin())
587                 return dir.filePath(*it, true);
588         return QString::null;
589 }
590