add more spacing
[personal-kdebase.git] / runtime / kioslave / archive / kio_archive.cpp
blob7c6ad33cabd458b4fe670719a8cda726367d9164
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 "kio_archive.h"
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <stdlib.h>
25 #include <unistd.h>
27 #include <QFile>
29 #include <kglobal.h>
30 #include <kurl.h>
31 #include <kdebug.h>
32 #include <kcomponentdata.h>
33 #include <ktar.h>
34 #include <kzip.h>
35 #include <kar.h>
36 #include <kmimetype.h>
37 #include <klocale.h>
38 #include <kde_file.h>
39 #include <kio/global.h>
41 #include <kuser.h>
43 using namespace KIO;
45 extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); }
47 int kdemain( int argc, char **argv )
49 KComponentData componentData( "kio_archive" );
51 kDebug(7109) << "Starting" << getpid();
53 if (argc != 4)
55 fprintf(stderr, "Usage: kio_archive protocol domain-socket1 domain-socket2\n");
56 exit(-1);
59 ArchiveProtocol slave(argv[2], argv[3]);
60 slave.dispatchLoop();
62 kDebug(7109) << "Done";
63 return 0;
66 ArchiveProtocol::ArchiveProtocol( const QByteArray &pool, const QByteArray &app ) : SlaveBase( "tar", pool, app )
68 kDebug( 7109 ) << "ArchiveProtocol::ArchiveProtocol";
69 m_archiveFile = 0L;
72 ArchiveProtocol::~ArchiveProtocol()
74 delete m_archiveFile;
77 bool ArchiveProtocol::checkNewFile( const KUrl & url, QString & path, KIO::Error& errorNum )
79 QString fullPath = url.path();
80 kDebug(7109) << "ArchiveProtocol::checkNewFile" << fullPath;
83 // Are we already looking at that file ?
84 if ( m_archiveFile && m_archiveName == fullPath.left(m_archiveName.length()) )
86 // Has it changed ?
87 KDE_struct_stat statbuf;
88 if ( KDE_stat( QFile::encodeName( m_archiveName ), &statbuf ) == 0 )
90 if ( m_mtime == statbuf.st_mtime )
92 path = fullPath.mid( m_archiveName.length() );
93 kDebug(7109) << "ArchiveProtocol::checkNewFile returning" << path;
94 return true;
98 kDebug(7109) << "Need to open a new file";
100 // Close previous file
101 if ( m_archiveFile )
103 m_archiveFile->close();
104 delete m_archiveFile;
105 m_archiveFile = 0L;
108 // Find where the tar file is in the full path
109 int pos = 0;
110 QString archiveFile;
111 path.clear();
113 int len = fullPath.length();
114 if ( len != 0 && fullPath[ len - 1 ] != '/' )
115 fullPath += '/';
117 kDebug(7109) << "the full path is" << fullPath;
118 KDE_struct_stat statbuf;
119 statbuf.st_mode = 0; // be sure to clear the directory bit
120 while ( (pos=fullPath.indexOf( '/', pos+1 )) != -1 )
122 QString tryPath = fullPath.left( pos );
123 kDebug(7109) << fullPath << "trying" << tryPath;
124 if ( KDE_stat( QFile::encodeName(tryPath), &statbuf ) == -1 )
126 // We are not in the file system anymore, either we have already enough data or we will never get any useful data anymore
127 break;
129 if ( !S_ISDIR(statbuf.st_mode) )
131 archiveFile = tryPath;
132 m_mtime = statbuf.st_mtime;
133 #ifdef Q_WS_WIN // st_uid and st_gid provides no information
134 m_user.clear();
135 m_group.clear();
136 #else
137 KUser user(statbuf.st_uid);
138 m_user = user.loginName();
139 KUserGroup group(statbuf.st_gid);
140 m_group = group.name();
141 #endif
142 path = fullPath.mid( pos + 1 );
143 kDebug(7109).nospace() << "fullPath=" << fullPath << " path=" << path;
144 len = path.length();
145 if ( len > 1 )
147 if ( path[ len - 1 ] == '/' )
148 path.truncate( len - 1 );
150 else
151 path = QString::fromLatin1("/");
152 kDebug(7109).nospace() << "Found. archiveFile=" << archiveFile << " path=" << path;
153 break;
156 if ( archiveFile.isEmpty() )
158 kDebug(7109) << "ArchiveProtocol::checkNewFile: not found";
159 if ( S_ISDIR(statbuf.st_mode) ) // Was the last stat about a directory?
161 // Too bad, it is a directory, not an archive.
162 kDebug(7109) << "Path is a directory, not an archive.";
163 errorNum = KIO::ERR_IS_DIRECTORY;
165 else
166 errorNum = KIO::ERR_DOES_NOT_EXIST;
167 return false;
170 // Open new file
171 if ( url.protocol() == "tar" ) {
172 kDebug(7109) << "Opening KTar on" << archiveFile;
173 m_archiveFile = new KTar( archiveFile );
174 } else if ( url.protocol() == "ar" ) {
175 kDebug(7109) << "Opening KAr on " << archiveFile;
176 m_archiveFile = new KAr( archiveFile );
177 } else if ( url.protocol() == "zip" ) {
178 kDebug(7109) << "Opening KZip on " << archiveFile;
179 m_archiveFile = new KZip( archiveFile );
180 } else {
181 kWarning(7109) << "Protocol" << url.protocol() << "not supported by this IOSlave" ;
182 errorNum = KIO::ERR_UNSUPPORTED_PROTOCOL;
183 return false;
186 if ( !m_archiveFile->open( QIODevice::ReadOnly ) )
188 kDebug(7109) << "Opening" << archiveFile << "failed.";
189 delete m_archiveFile;
190 m_archiveFile = 0L;
191 errorNum = KIO::ERR_CANNOT_OPEN_FOR_READING;
192 return false;
195 m_archiveName = archiveFile;
196 return true;
200 void ArchiveProtocol::createRootUDSEntry( KIO::UDSEntry & entry )
202 entry.clear();
203 entry.insert( KIO::UDSEntry::UDS_NAME, "." );
204 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR );
205 entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, m_mtime );
206 //entry.insert( KIO::UDSEntry::UDS_ACCESS, 07777 ); // fake 'x' permissions, this is a pseudo-directory
207 entry.insert( KIO::UDSEntry::UDS_USER, m_user);
208 entry.insert( KIO::UDSEntry::UDS_GROUP, m_group);
211 void ArchiveProtocol::createUDSEntry( const KArchiveEntry * archiveEntry, UDSEntry & entry )
213 entry.clear();
214 entry.insert( KIO::UDSEntry::UDS_NAME, archiveEntry->name() );
215 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, archiveEntry->permissions() & S_IFMT ); // keep file type only
216 entry.insert( KIO::UDSEntry::UDS_SIZE, archiveEntry->isFile() ? ((KArchiveFile *)archiveEntry)->size() : 0L );
217 entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, archiveEntry->date());
218 entry.insert( KIO::UDSEntry::UDS_ACCESS, archiveEntry->permissions() & 07777 ); // keep permissions only
219 entry.insert( KIO::UDSEntry::UDS_USER, archiveEntry->user());
220 entry.insert( KIO::UDSEntry::UDS_GROUP, archiveEntry->group());
221 entry.insert( KIO::UDSEntry::UDS_LINK_DEST, archiveEntry->symLinkTarget());
224 void ArchiveProtocol::listDir( const KUrl & url )
226 kDebug( 7109 ) << "ArchiveProtocol::listDir" << url.url();
228 QString path;
229 KIO::Error errorNum;
230 if ( !checkNewFile( url, path, errorNum ) )
232 if ( errorNum == KIO::ERR_CANNOT_OPEN_FOR_READING )
234 // If we cannot open, it might be a problem with the archive header (e.g. unsupported format)
235 // Therefore give a more specific error message
236 error( KIO::ERR_SLAVE_DEFINED,
237 i18n( "Could not open the file, probably due to an unsupported file format.\n%1",
238 url.prettyUrl() ) );
239 return;
241 else if ( errorNum != ERR_IS_DIRECTORY )
243 // We have any other error
244 error( errorNum, url.prettyUrl() );
245 return;
247 // It's a real dir -> redirect
248 KUrl redir;
249 redir.setPath( url.path() );
250 kDebug( 7109 ) << "Ok, redirection to" << redir.url();
251 redirection( redir );
252 finished();
253 // And let go of the tar file - for people who want to unmount a cdrom after that
254 delete m_archiveFile;
255 m_archiveFile = 0L;
256 return;
259 if ( path.isEmpty() )
261 KUrl redir( url.protocol() + QString::fromLatin1( ":/") );
262 kDebug( 7109 ) << "url.path()=" << url.path();
263 redir.setPath( url.path() + QString::fromLatin1("/") );
264 kDebug( 7109 ) << "ArchiveProtocol::listDir: redirection" << redir.url();
265 redirection( redir );
266 finished();
267 return;
270 kDebug( 7109 ) << "checkNewFile done";
271 const KArchiveDirectory* root = m_archiveFile->directory();
272 const KArchiveDirectory* dir;
273 if (!path.isEmpty() && path != "/")
275 kDebug(7109) << "Looking for entry" << path;
276 const KArchiveEntry* e = root->entry( path );
277 if ( !e )
279 error( KIO::ERR_DOES_NOT_EXIST, url.prettyUrl() );
280 return;
282 if ( ! e->isDirectory() )
284 error( KIO::ERR_IS_FILE, url.prettyUrl() );
285 return;
287 dir = (KArchiveDirectory*)e;
288 } else {
289 dir = root;
292 const QStringList l = dir->entries();
293 totalSize( l.count() );
295 UDSEntry entry;
296 if (!l.contains(".")) {
297 createRootUDSEntry(entry);
298 listEntry(entry, false);
301 QStringList::const_iterator it = l.begin();
302 for( ; it != l.end(); ++it )
304 kDebug(7109) << (*it);
305 const KArchiveEntry* archiveEntry = dir->entry( (*it) );
307 createUDSEntry( archiveEntry, entry );
309 listEntry( entry, false );
312 listEntry( entry, true ); // ready
314 finished();
316 kDebug( 7109 ) << "ArchiveProtocol::listDir done";
319 void ArchiveProtocol::stat( const KUrl & url )
321 QString path;
322 UDSEntry entry;
323 KIO::Error errorNum;
324 if ( !checkNewFile( url, path, errorNum ) )
326 // We may be looking at a real directory - this happens
327 // when pressing up after being in the root of an archive
328 if ( errorNum == KIO::ERR_CANNOT_OPEN_FOR_READING )
330 // If we cannot open, it might be a problem with the archive header (e.g. unsupported format)
331 // Therefore give a more specific error message
332 error( KIO::ERR_SLAVE_DEFINED,
333 i18n( "Could not open the file, probably due to an unsupported file format.\n%1",
334 url.prettyUrl() ) );
335 return;
337 else if ( errorNum != ERR_IS_DIRECTORY )
339 // We have any other error
340 error( errorNum, url.prettyUrl() );
341 return;
343 // Real directory. Return just enough information for KRun to work
344 entry.insert( KIO::UDSEntry::UDS_NAME, url.fileName());
345 kDebug( 7109 ).nospace() << "ArchiveProtocol::stat returning name=" << url.fileName();
347 KDE_struct_stat buff;
348 if ( KDE_stat( QFile::encodeName( url.path() ), &buff ) == -1 )
350 // Should not happen, as the file was already stated by checkNewFile
351 error( KIO::ERR_COULD_NOT_STAT, url.prettyUrl() );
352 return;
355 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, buff.st_mode & S_IFMT);
357 statEntry( entry );
359 finished();
361 // And let go of the tar file - for people who want to unmount a cdrom after that
362 delete m_archiveFile;
363 m_archiveFile = 0L;
364 return;
367 const KArchiveDirectory* root = m_archiveFile->directory();
368 const KArchiveEntry* archiveEntry;
369 if ( path.isEmpty() )
371 path = QString::fromLatin1( "/" );
372 archiveEntry = root;
373 } else {
374 archiveEntry = root->entry( path );
376 if ( !archiveEntry )
378 error( KIO::ERR_DOES_NOT_EXIST, url.prettyUrl() );
379 return;
382 createUDSEntry( archiveEntry, entry );
383 statEntry( entry );
385 finished();
388 void ArchiveProtocol::get( const KUrl & url )
390 kDebug( 7109 ) << "ArchiveProtocol::get" << url.url();
392 QString path;
393 KIO::Error errorNum;
394 if ( !checkNewFile( url, path, errorNum ) )
396 if ( errorNum == KIO::ERR_CANNOT_OPEN_FOR_READING )
398 // If we cannot open, it might be a problem with the archive header (e.g. unsupported format)
399 // Therefore give a more specific error message
400 error( KIO::ERR_SLAVE_DEFINED,
401 i18n( "Could not open the file, probably due to an unsupported file format.\n%1",
402 url.prettyUrl() ) );
403 return;
405 else
407 // We have any other error
408 error( errorNum, url.prettyUrl() );
409 return;
413 const KArchiveDirectory* root = m_archiveFile->directory();
414 const KArchiveEntry* archiveEntry = root->entry( path );
416 if ( !archiveEntry )
418 error( KIO::ERR_DOES_NOT_EXIST, url.prettyUrl() );
419 return;
421 if ( archiveEntry->isDirectory() )
423 error( KIO::ERR_IS_DIRECTORY, url.prettyUrl() );
424 return;
426 const KArchiveFile* archiveFileEntry = static_cast<const KArchiveFile *>(archiveEntry);
427 if ( !archiveEntry->symLinkTarget().isEmpty() )
429 kDebug(7109) << "Redirection to" << archiveEntry->symLinkTarget();
430 KUrl realURL( url, archiveEntry->symLinkTarget() );
431 kDebug(7109).nospace() << "realURL=" << realURL.url();
432 redirection( realURL );
433 finished();
434 return;
437 //kDebug(7109) << "Preparing to get the archive data";
440 * The easy way would be to get the data by calling archiveFileEntry->data()
441 * However this has drawbacks:
442 * - the complete file must be read into the memory
443 * - errors are skipped, resulting in an empty file
446 QIODevice* io = archiveFileEntry->createDevice();
448 if (!io)
450 error( KIO::ERR_SLAVE_DEFINED,
451 i18n( "The archive file could not be opened, perhaps because the format is unsupported.\n%1" ,
452 url.prettyUrl() ) );
453 return;
456 if ( !io->open( QIODevice::ReadOnly ) )
458 error( KIO::ERR_CANNOT_OPEN_FOR_READING, url.prettyUrl() );
459 delete io;
460 return;
463 totalSize( archiveFileEntry->size() );
465 // Size of a QIODevice read. It must be large enough so that the mime type check will not fail
466 const qint64 maxSize = 0x100000; // 1MB
468 qint64 bufferSize = qMin( maxSize, archiveFileEntry->size() );
469 QByteArray buffer;
470 buffer.resize( bufferSize );
471 if ( buffer.isEmpty() && bufferSize > 0 )
473 // Something went wrong
474 error( KIO::ERR_OUT_OF_MEMORY, url.prettyUrl() );
475 delete io;
476 return;
479 bool firstRead = true;
481 // How much file do we still have to process?
482 qint64 fileSize = archiveFileEntry->size();
483 KIO::filesize_t processed = 0;
485 while ( !io->atEnd() && fileSize > 0 )
487 if ( !firstRead )
489 bufferSize = qMin( maxSize, fileSize );
490 buffer.resize( bufferSize );
492 const qint64 read = io->read( buffer.data(), buffer.size() ); // Avoid to use bufferSize here, in case something went wrong.
493 if ( read != bufferSize )
495 kWarning(7109) << "Read" << read << "bytes but expected" << bufferSize ;
496 error( KIO::ERR_COULD_NOT_READ, url.prettyUrl() );
497 delete io;
498 return;
500 if ( firstRead )
502 // We use the magic one the first data read
503 // (As magic detection is about fixed positions, we can be sure that it is enough data.)
504 KMimeType::Ptr mime = KMimeType::findByNameAndContent( path, buffer );
505 kDebug(7109) << "Emitting mimetype" << mime->name();
506 mimeType( mime->name() );
507 firstRead = false;
509 data( buffer );
510 processed += read;
511 processedSize( processed );
512 fileSize -= bufferSize;
514 io->close();
515 delete io;
517 data( QByteArray() );
519 finished();
523 In case someone wonders how the old filter stuff looked like : :)
524 void TARProtocol::slotData(void *_p, int _len)
526 switch (m_cmd) {
527 case CMD_PUT:
528 assert(m_pFilter);
529 m_pFilter->send(_p, _len);
530 break;
531 default:
532 abort();
533 break;
537 void TARProtocol::slotDataEnd()
539 switch (m_cmd) {
540 case CMD_PUT:
541 assert(m_pFilter && m_pJob);
542 m_pFilter->finish();
543 m_pJob->dataEnd();
544 m_cmd = CMD_NONE;
545 break;
546 default:
547 abort();
548 break;
552 void TARProtocol::jobData(void *_p, int _len)
554 switch (m_cmd) {
555 case CMD_GET:
556 assert(m_pFilter);
557 m_pFilter->send(_p, _len);
558 break;
559 case CMD_COPY:
560 assert(m_pFilter);
561 m_pFilter->send(_p, _len);
562 break;
563 default:
564 abort();
568 void TARProtocol::jobDataEnd()
570 switch (m_cmd) {
571 case CMD_GET:
572 assert(m_pFilter);
573 m_pFilter->finish();
574 dataEnd();
575 break;
576 case CMD_COPY:
577 assert(m_pFilter);
578 m_pFilter->finish();
579 m_pJob->dataEnd();
580 break;
581 default:
582 abort();
586 void TARProtocol::filterData(void *_p, int _len)
588 debug("void TARProtocol::filterData");
589 switch (m_cmd) {
590 case CMD_GET:
591 data(_p, _len);
592 break;
593 case CMD_PUT:
594 assert (m_pJob);
595 m_pJob->data(_p, _len);
596 break;
597 case CMD_COPY:
598 assert(m_pJob);
599 m_pJob->data(_p, _len);
600 break;
601 default:
602 abort();
607 // kate: space-indent on; indent-width 4; replace-tabs on;