Make a branch to make krunner Good Enough For Aaron™.
[kdebase/uwolfer.git] / runtime / kioslave / archive / kio_archive.cpp
blob18536eab87b46680429b233f7d79572aa349a01a
1 /* This file is part of the KDE libraries
2 Copyright (C) 2000 David Faure <faure@kde.org>
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <stdlib.h>
23 #include <unistd.h>
25 #include <QFile>
27 #include <kglobal.h>
28 #include <kurl.h>
29 #include <kdebug.h>
30 #include <kcomponentdata.h>
31 #include <ktar.h>
32 #include <kzip.h>
33 #include <kar.h>
34 #include <kmimetype.h>
35 #include <klocale.h>
36 #include <kde_file.h>
37 #include <kio/global.h>
39 #include "kio_archive.h"
40 #include <kuser.h>
42 using namespace KIO;
44 extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); }
46 int kdemain( int argc, char **argv )
48 KComponentData componentData( "kio_archive" );
50 kDebug(7109) << "Starting" << getpid();
52 if (argc != 4)
54 fprintf(stderr, "Usage: kio_archive protocol domain-socket1 domain-socket2\n");
55 exit(-1);
58 ArchiveProtocol slave(argv[2], argv[3]);
59 slave.dispatchLoop();
61 kDebug(7109) << "Done";
62 return 0;
65 ArchiveProtocol::ArchiveProtocol( const QByteArray &pool, const QByteArray &app ) : SlaveBase( "tar", pool, app )
67 kDebug( 7109 ) << "ArchiveProtocol::ArchiveProtocol";
68 m_archiveFile = 0L;
71 ArchiveProtocol::~ArchiveProtocol()
73 delete m_archiveFile;
76 bool ArchiveProtocol::checkNewFile( const KUrl & url, QString & path, KIO::Error& errorNum )
78 QString fullPath = url.path();
79 kDebug(7109) << "ArchiveProtocol::checkNewFile" << fullPath;
82 // Are we already looking at that file ?
83 if ( m_archiveFile && m_archiveName == fullPath.left(m_archiveName.length()) )
85 // Has it changed ?
86 KDE_struct_stat statbuf;
87 if ( KDE_stat( QFile::encodeName( m_archiveName ), &statbuf ) == 0 )
89 if ( m_mtime == statbuf.st_mtime )
91 path = fullPath.mid( m_archiveName.length() );
92 kDebug(7109) << "ArchiveProtocol::checkNewFile returning" << path;
93 return true;
97 kDebug(7109) << "Need to open a new file";
99 // Close previous file
100 if ( m_archiveFile )
102 m_archiveFile->close();
103 delete m_archiveFile;
104 m_archiveFile = 0L;
107 // Find where the tar file is in the full path
108 int pos = 0;
109 QString archiveFile;
110 path.clear();
112 int len = fullPath.length();
113 if ( len != 0 && fullPath[ len - 1 ] != '/' )
114 fullPath += '/';
116 kDebug(7109) << "the full path is" << fullPath;
117 KDE_struct_stat statbuf;
118 statbuf.st_mode = 0; // be sure to clear the directory bit
119 while ( (pos=fullPath.indexOf( '/', pos+1 )) != -1 )
121 QString tryPath = fullPath.left( pos );
122 kDebug(7109) << fullPath << "trying" << tryPath;
123 if ( KDE_stat( QFile::encodeName(tryPath), &statbuf ) == -1 )
125 // We are not in the file system anymore, either we have already enough data or we will never get any useful data anymore
126 break;
128 if ( !S_ISDIR(statbuf.st_mode) )
130 archiveFile = tryPath;
131 m_mtime = statbuf.st_mtime;
132 #ifdef Q_WS_WIN // st_uid and st_gid provides no information
133 m_user.clear();
134 m_group.clear();
135 #else
136 KUser user(statbuf.st_uid);
137 m_user = user.loginName();
138 KUserGroup group(statbuf.st_gid);
139 m_group = group.name();
140 #endif
141 path = fullPath.mid( pos + 1 );
142 kDebug(7109).nospace() << "fullPath=" << fullPath << " path=" << path;
143 len = path.length();
144 if ( len > 1 )
146 if ( path[ len - 1 ] == '/' )
147 path.truncate( len - 1 );
149 else
150 path = QString::fromLatin1("/");
151 kDebug(7109).nospace() << "Found. archiveFile=" << archiveFile << " path=" << path;
152 break;
155 if ( archiveFile.isEmpty() )
157 kDebug(7109) << "ArchiveProtocol::checkNewFile: not found";
158 if ( S_ISDIR(statbuf.st_mode) ) // Was the last stat about a directory?
160 // Too bad, it is a directory, not an archive.
161 kDebug(7109) << "Path is a directory, not an archive.";
162 errorNum = KIO::ERR_IS_DIRECTORY;
164 else
165 errorNum = KIO::ERR_DOES_NOT_EXIST;
166 return false;
169 // Open new file
170 if ( url.protocol() == "tar" ) {
171 kDebug(7109) << "Opening KTar on" << archiveFile;
172 m_archiveFile = new KTar( archiveFile );
173 } else if ( url.protocol() == "ar" ) {
174 kDebug(7109) << "Opening KAr on " << archiveFile;
175 m_archiveFile = new KAr( archiveFile );
176 } else if ( url.protocol() == "zip" ) {
177 kDebug(7109) << "Opening KZip on " << archiveFile;
178 m_archiveFile = new KZip( archiveFile );
179 } else {
180 kWarning(7109) << "Protocol" << url.protocol() << "not supported by this IOSlave" ;
181 errorNum = KIO::ERR_UNSUPPORTED_PROTOCOL;
182 return false;
185 if ( !m_archiveFile->open( QIODevice::ReadOnly ) )
187 kDebug(7109) << "Opening" << archiveFile << "failed.";
188 delete m_archiveFile;
189 m_archiveFile = 0L;
190 errorNum = KIO::ERR_CANNOT_OPEN_FOR_READING;
191 return false;
194 m_archiveName = archiveFile;
195 return true;
199 void ArchiveProtocol::createRootUDSEntry( KIO::UDSEntry & entry )
201 entry.clear();
202 entry.insert( KIO::UDSEntry::UDS_NAME, "." );
203 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR );
204 entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, m_mtime );
205 //entry.insert( KIO::UDSEntry::UDS_ACCESS, 07777 ); // fake 'x' permissions, this is a pseudo-directory
206 entry.insert( KIO::UDSEntry::UDS_USER, m_user);
207 entry.insert( KIO::UDSEntry::UDS_GROUP, m_group);
210 void ArchiveProtocol::createUDSEntry( const KArchiveEntry * archiveEntry, UDSEntry & entry )
212 entry.clear();
213 entry.insert( KIO::UDSEntry::UDS_NAME, archiveEntry->name() );
214 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, archiveEntry->permissions() & S_IFMT ); // keep file type only
215 entry.insert( KIO::UDSEntry::UDS_SIZE, archiveEntry->isFile() ? ((KArchiveFile *)archiveEntry)->size() : 0L );
216 entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, archiveEntry->date());
217 entry.insert( KIO::UDSEntry::UDS_ACCESS, archiveEntry->permissions() & 07777 ); // keep permissions only
218 entry.insert( KIO::UDSEntry::UDS_USER, archiveEntry->user());
219 entry.insert( KIO::UDSEntry::UDS_GROUP, archiveEntry->group());
220 entry.insert( KIO::UDSEntry::UDS_LINK_DEST, archiveEntry->symLinkTarget());
223 void ArchiveProtocol::listDir( const KUrl & url )
225 kDebug( 7109 ) << "ArchiveProtocol::listDir" << url.url();
227 QString path;
228 KIO::Error errorNum;
229 if ( !checkNewFile( url, path, errorNum ) )
231 if ( errorNum == KIO::ERR_CANNOT_OPEN_FOR_READING )
233 // If we cannot open, it might be a problem with the archive header (e.g. unsupported format)
234 // Therefore give a more specific error message
235 error( KIO::ERR_SLAVE_DEFINED,
236 i18n( "Could not open the file, probably due to an unsupported file format.\n%1",
237 url.prettyUrl() ) );
238 return;
240 else if ( errorNum != ERR_IS_DIRECTORY )
242 // We have any other error
243 error( errorNum, url.prettyUrl() );
244 return;
246 // It's a real dir -> redirect
247 KUrl redir;
248 redir.setPath( url.path() );
249 kDebug( 7109 ) << "Ok, redirection to" << redir.url();
250 redirection( redir );
251 finished();
252 // And let go of the tar file - for people who want to unmount a cdrom after that
253 delete m_archiveFile;
254 m_archiveFile = 0L;
255 return;
258 if ( path.isEmpty() )
260 KUrl redir( url.protocol() + QString::fromLatin1( ":/") );
261 kDebug( 7109 ) << "url.path()=" << url.path();
262 redir.setPath( url.path() + QString::fromLatin1("/") );
263 kDebug( 7109 ) << "ArchiveProtocol::listDir: redirection" << redir.url();
264 redirection( redir );
265 finished();
266 return;
269 kDebug( 7109 ) << "checkNewFile done";
270 const KArchiveDirectory* root = m_archiveFile->directory();
271 const KArchiveDirectory* dir;
272 if (!path.isEmpty() && path != "/")
274 kDebug(7109) << "Looking for entry" << path;
275 const KArchiveEntry* e = root->entry( path );
276 if ( !e )
278 error( KIO::ERR_DOES_NOT_EXIST, url.prettyUrl() );
279 return;
281 if ( ! e->isDirectory() )
283 error( KIO::ERR_IS_FILE, url.prettyUrl() );
284 return;
286 dir = (KArchiveDirectory*)e;
287 } else {
288 dir = root;
291 const QStringList l = dir->entries();
292 totalSize( l.count() );
294 UDSEntry entry;
295 if (!l.contains(".")) {
296 createRootUDSEntry(entry);
297 listEntry(entry, false);
300 QStringList::const_iterator it = l.begin();
301 for( ; it != l.end(); ++it )
303 kDebug(7109) << (*it);
304 const KArchiveEntry* archiveEntry = dir->entry( (*it) );
306 createUDSEntry( archiveEntry, entry );
308 listEntry( entry, false );
311 listEntry( entry, true ); // ready
313 finished();
315 kDebug( 7109 ) << "ArchiveProtocol::listDir done";
318 void ArchiveProtocol::stat( const KUrl & url )
320 QString path;
321 UDSEntry entry;
322 KIO::Error errorNum;
323 if ( !checkNewFile( url, path, errorNum ) )
325 // We may be looking at a real directory - this happens
326 // when pressing up after being in the root of an archive
327 if ( errorNum == KIO::ERR_CANNOT_OPEN_FOR_READING )
329 // If we cannot open, it might be a problem with the archive header (e.g. unsupported format)
330 // Therefore give a more specific error message
331 error( KIO::ERR_SLAVE_DEFINED,
332 i18n( "Could not open the file, probably due to an unsupported file format.\n%1",
333 url.prettyUrl() ) );
334 return;
336 else if ( errorNum != ERR_IS_DIRECTORY )
338 // We have any other error
339 error( errorNum, url.prettyUrl() );
340 return;
342 // Real directory. Return just enough information for KRun to work
343 entry.insert( KIO::UDSEntry::UDS_NAME, url.fileName());
344 kDebug( 7109 ).nospace() << "ArchiveProtocol::stat returning name=" << url.fileName();
346 KDE_struct_stat buff;
347 if ( KDE_stat( QFile::encodeName( url.path() ), &buff ) == -1 )
349 // Should not happen, as the file was already stated by checkNewFile
350 error( KIO::ERR_COULD_NOT_STAT, url.prettyUrl() );
351 return;
354 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, buff.st_mode & S_IFMT);
356 statEntry( entry );
358 finished();
360 // And let go of the tar file - for people who want to unmount a cdrom after that
361 delete m_archiveFile;
362 m_archiveFile = 0L;
363 return;
366 const KArchiveDirectory* root = m_archiveFile->directory();
367 const KArchiveEntry* archiveEntry;
368 if ( path.isEmpty() )
370 path = QString::fromLatin1( "/" );
371 archiveEntry = root;
372 } else {
373 archiveEntry = root->entry( path );
375 if ( !archiveEntry )
377 error( KIO::ERR_DOES_NOT_EXIST, url.prettyUrl() );
378 return;
381 createUDSEntry( archiveEntry, entry );
382 statEntry( entry );
384 finished();
387 void ArchiveProtocol::get( const KUrl & url )
389 kDebug( 7109 ) << "ArchiveProtocol::get" << url.url();
391 QString path;
392 KIO::Error errorNum;
393 if ( !checkNewFile( url, path, errorNum ) )
395 if ( errorNum == KIO::ERR_CANNOT_OPEN_FOR_READING )
397 // If we cannot open, it might be a problem with the archive header (e.g. unsupported format)
398 // Therefore give a more specific error message
399 error( KIO::ERR_SLAVE_DEFINED,
400 i18n( "Could not open the file, probably due to an unsupported file format.\n%1",
401 url.prettyUrl() ) );
402 return;
404 else
406 // We have any other error
407 error( errorNum, url.prettyUrl() );
408 return;
412 const KArchiveDirectory* root = m_archiveFile->directory();
413 const KArchiveEntry* archiveEntry = root->entry( path );
415 if ( !archiveEntry )
417 error( KIO::ERR_DOES_NOT_EXIST, url.prettyUrl() );
418 return;
420 if ( archiveEntry->isDirectory() )
422 error( KIO::ERR_IS_DIRECTORY, url.prettyUrl() );
423 return;
425 const KArchiveFile* archiveFileEntry = static_cast<const KArchiveFile *>(archiveEntry);
426 if ( !archiveEntry->symLinkTarget().isEmpty() )
428 kDebug(7109) << "Redirection to" << archiveEntry->symLinkTarget();
429 KUrl realURL( url, archiveEntry->symLinkTarget() );
430 kDebug(7109).nospace() << "realURL=" << realURL.url();
431 redirection( realURL );
432 finished();
433 return;
436 //kDebug(7109) << "Preparing to get the archive data";
439 * The easy way would be to get the data by calling archiveFileEntry->data()
440 * However this has drawbacks:
441 * - the complete file must be read into the memory
442 * - errors are skipped, resulting in an empty file
445 QIODevice* io = archiveFileEntry->createDevice();
447 if (!io)
449 error( KIO::ERR_SLAVE_DEFINED,
450 i18n( "The archive file could not be opened, perhaps because the format is unsupported.\n%1" ,
451 url.prettyUrl() ) );
452 return;
455 if ( !io->open( QIODevice::ReadOnly ) )
457 error( KIO::ERR_CANNOT_OPEN_FOR_READING, url.prettyUrl() );
458 delete io;
459 return;
462 totalSize( archiveFileEntry->size() );
464 // Size of a QIODevice read. It must be large enough so that the mime type check will not fail
465 const qint64 maxSize = 0x100000; // 1MB
467 qint64 bufferSize = qMin( maxSize, archiveFileEntry->size() );
468 QByteArray buffer;
469 buffer.resize( bufferSize );
470 if ( buffer.isEmpty() && bufferSize > 0 )
472 // Something went wrong
473 error( KIO::ERR_OUT_OF_MEMORY, url.prettyUrl() );
474 delete io;
475 return;
478 bool firstRead = true;
480 // How much file do we still have to process?
481 qint64 fileSize = archiveFileEntry->size();
482 KIO::filesize_t processed = 0;
484 while ( !io->atEnd() && fileSize > 0 )
486 if ( !firstRead )
488 bufferSize = qMin( maxSize, fileSize );
489 buffer.resize( bufferSize );
491 const qint64 read = io->read( buffer.data(), buffer.size() ); // Avoid to use bufferSize here, in case something went wrong.
492 if ( read != bufferSize )
494 kWarning(7109) << "Read" << read << "bytes but expected" << bufferSize ;
495 error( KIO::ERR_COULD_NOT_READ, url.prettyUrl() );
496 delete io;
497 return;
499 if ( firstRead )
501 // We use the magic one the first data read
502 // (As magic detection is about fixed positions, we can be sure that it is enough data.)
503 KMimeType::Ptr mime = KMimeType::findByNameAndContent( path, buffer );
504 kDebug(7109) << "Emitting mimetype" << mime->name();
505 mimeType( mime->name() );
506 firstRead = false;
508 data( buffer );
509 processed += read;
510 processedSize( processed );
511 fileSize -= bufferSize;
513 io->close();
514 delete io;
516 data( QByteArray() );
518 finished();
522 In case someone wonders how the old filter stuff looked like : :)
523 void TARProtocol::slotData(void *_p, int _len)
525 switch (m_cmd) {
526 case CMD_PUT:
527 assert(m_pFilter);
528 m_pFilter->send(_p, _len);
529 break;
530 default:
531 abort();
532 break;
536 void TARProtocol::slotDataEnd()
538 switch (m_cmd) {
539 case CMD_PUT:
540 assert(m_pFilter && m_pJob);
541 m_pFilter->finish();
542 m_pJob->dataEnd();
543 m_cmd = CMD_NONE;
544 break;
545 default:
546 abort();
547 break;
551 void TARProtocol::jobData(void *_p, int _len)
553 switch (m_cmd) {
554 case CMD_GET:
555 assert(m_pFilter);
556 m_pFilter->send(_p, _len);
557 break;
558 case CMD_COPY:
559 assert(m_pFilter);
560 m_pFilter->send(_p, _len);
561 break;
562 default:
563 abort();
567 void TARProtocol::jobDataEnd()
569 switch (m_cmd) {
570 case CMD_GET:
571 assert(m_pFilter);
572 m_pFilter->finish();
573 dataEnd();
574 break;
575 case CMD_COPY:
576 assert(m_pFilter);
577 m_pFilter->finish();
578 m_pJob->dataEnd();
579 break;
580 default:
581 abort();
585 void TARProtocol::filterData(void *_p, int _len)
587 debug("void TARProtocol::filterData");
588 switch (m_cmd) {
589 case CMD_GET:
590 data(_p, _len);
591 break;
592 case CMD_PUT:
593 assert (m_pJob);
594 m_pJob->data(_p, _len);
595 break;
596 case CMD_COPY:
597 assert(m_pJob);
598 m_pJob->data(_p, _len);
599 break;
600 default:
601 abort();
606 // kate: space-indent on; indent-width 4; replace-tabs on;