/* * orphanedfilesdialog.cc * * Copyright (c) 2002, 2003 Frerich Raabe * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. For licensing and distribution details, check the * accompanying file 'COPYING'. */ #include "databasewrapper.h" #include "orphanedfilesdialog.h" #include "portmgr.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { bool isPrime( int number ) { if ( number <= 1 ) return false; int divisor = int( sqrt( number ) ); while ( number % divisor != 0 ) --divisor; return divisor == 1; } int nextPrime( int number ) { int i = number; while ( !isPrime( i ) ) ++i; return i; } } using namespace Barry; class DirChangeEvent : public QCustomEvent { public: static const int Id = 10001; DirChangeEvent( const QString &dir ) : QCustomEvent( Id ), m_dir( dir ) { } QString dir() const { return m_dir; } private: QString m_dir; }; class FoundFileEvent : public QCustomEvent { public: static const int Id = 10002; FoundFileEvent( const QString &name ) : QCustomEvent( Id ), m_name( name ) { } QString name() const { return m_name; } private: QString m_name; }; class TraverserDoneEvent : public QCustomEvent { public: static const int Id = 10003; TraverserDoneEvent() : QCustomEvent( Id ) { } }; class TraverserListEvent : public QCustomEvent { public: static const int Id = 10004; TraverserListEvent( const QPtrList &eventList ) : QCustomEvent( Id ), m_eventList( eventList ) { } const QPtrList &eventList() const { return m_eventList; } private: QPtrList m_eventList; }; class DirTraverserThread : public QThread { public: DirTraverserThread( QWidget *parent, const QStringList &startDirs, const QDict &ignoredFiles ); void abort() { m_abort = true; } protected: virtual void run(); private: void scanDir( const QString &path ); QWidget *m_parent; QStringList m_startDirs; QDict m_ignoredFiles; bool m_abort; QPtrList m_eventList; QTime m_timeSnapshot; }; DirTraverserThread::DirTraverserThread( QWidget *parent, const QStringList &startDirs, const QDict &ignoredFiles ) : m_parent( parent ), m_startDirs( startDirs ), m_ignoredFiles( ignoredFiles ), m_abort( false ) { } void DirTraverserThread::run() { m_timeSnapshot = QTime::currentTime(); QStringList::ConstIterator it = m_startDirs.begin(); QStringList::ConstIterator end = m_startDirs.end(); for ( ; it != end; ++it ) scanDir( *it ); kapp->postEvent( m_parent, new TraverserDoneEvent ); } void DirTraverserThread::scanDir( const QString &path ) { if ( m_abort ) return; if ( m_ignoredFiles.find( path ) != 0 ) return; DIR *dirp = opendir( path.latin1() ); if ( !dirp ) return; m_eventList.append( new DirChangeEvent( path ) ); struct dirent *dp; while ( ( dp = readdir( dirp ) ) != 0 ) { if ( dp->d_type == DT_DIR && strcmp( dp->d_name, "." ) != 0 && strcmp( dp->d_name, ".." ) != 0 ) { const QString absPath = path + QString::fromLatin1( "/" ) + QString::fromLatin1( dp->d_name, dp->d_namlen ); if ( access( absPath.latin1(), R_OK | X_OK ) == 0 ) scanDir( absPath ); } else if ( dp->d_type == DT_REG ) { QDir d( path ); const QString canonPath = d.canonicalPath(); const QString absName = canonPath + QString::fromLatin1( "/" ) + QString::fromLatin1( dp->d_name, dp->d_namlen ); if ( m_ignoredFiles.find( absName ) == 0 ) m_eventList.append( new FoundFileEvent( absName ) ); } if ( m_timeSnapshot.msecsTo( QTime::currentTime() ) > 100 ) { kapp->postEvent( m_parent, new TraverserListEvent( m_eventList ) ); m_eventList.clear(); m_timeSnapshot = QTime::currentTime(); } if ( m_abort ) break; } closedir( dirp ); } OrphanedFilesDialog::OrphanedFilesDialog( QWidget *parent, const char *name ) : KDialogBase( parent, name, true, i18n( "Searching for orphaned files..." ), Cancel, Cancel, true ) { resize( 500, 300 ); QVBox *mainWidget = makeVBoxMainWidget(); m_statusText = new KSqueezedTextLabel( i18n( "Scanning: %1" ).arg( QString::null ), mainWidget, "m_statusText" ); m_foundFiles = new KFileDetailView( mainWidget, "m_foundFiles" ); m_foundFiles->setSorting( QDir::Unsorted ); m_foundFiles->hide(); // Disable clicking to work around a crash in KFileDetailView m_foundFiles->header()->setClickEnabled( false ); connect( m_foundFiles, SIGNAL( contextMenu( KListView *, QListViewItem *, const QPoint & ) ), this, SLOT( showContextMenu( KListView *, QListViewItem *, const QPoint & ) ) ); } void OrphanedFilesDialog::show() { KDialogBase::show(); search(); } QStringList OrphanedFilesDialog::determineRegisteredFiles() { QStringList registeredFiles; /* We know that all the pkgdb can map a filename to a package, so we * get all keys and strip those which don't start with a slash, that * should be the list of registered files. */ registeredFiles = PortMgr::self().pkgDb().getAllKeys(); /* Since ~95% of the keys in the pkgdb are filenames, and they come first, * we start searching from the end and just strip all non-filename * keys. */ QStringList::Iterator it = registeredFiles.end(); --it; while ( ( *it )[ 0 ] != '/' && it != registeredFiles.begin() ) --it; registeredFiles.erase( ++it, registeredFiles.end() ); return registeredFiles; } QStringList OrphanedFilesDialog::determineIgnoredFiles() { KConfig *cfg = kapp->config(); cfg->setGroup( QString::fromLatin1( "Orphaned files" ) ); return cfg->readListEntry( "Ignored files" ); } void OrphanedFilesDialog::search() { /* Merge the lists of registered and ignored files into a QDict so that * the directory traverser thread wastes less time with lookups. */ const QStringList allFiles = determineRegisteredFiles() + determineIgnoredFiles(); QDict ignoredDict( nextPrime( allFiles.count() ) ); ignoredDict.setAutoDelete( true ); QStringList::ConstIterator it = allFiles.begin(); QStringList::ConstIterator end = allFiles.end(); for ( ; it != end; ++it ) { QString s = *it; if ( s.at( s.length() - 1 ) == '/' ) s.remove( s.length() - 1, 1 ); ignoredDict.insert( s, new bool( true ) ); } m_traverserThread = new DirTraverserThread( this, PortMgr::self().prefixes(), ignoredDict ); m_traverserThread->start(); } void OrphanedFilesDialog::slotCancel() { if ( m_traverserThread ) { m_statusText->setText( i18n( "Aborting..." ) ); m_statusText->update(); m_traverserThread->abort(); m_traverserThread->wait( 5 ); delete m_traverserThread; } reject(); } void OrphanedFilesDialog::showContextMenu( KListView *, QListViewItem *, const QPoint &p ) { QPopupMenu *menu = new QPopupMenu( this ); menu->insertItem( SmallIcon( QString::fromLatin1( "misc" ) ), i18n( "Properties..." ), this, SLOT( showProperties() ) ); menu->insertItem( SmallIcon( QString::fromLatin1( "fileopen" ) ), i18n( "Open..." ), this, SLOT( openFile() ) ); menu->insertSeparator(); menu->insertItem( SmallIcon( QString::fromLatin1( "editdelete" ) ), i18n( "Delete..." ), this, SLOT( deleteFile() ) ); menu->insertSeparator(); menu->insertItem( i18n( "Add file to ignore list" ), this, SLOT( addFileToIgnoreList() ) ); menu->insertItem( i18n( "Add directory to ignore list" ), this, SLOT( addDirectoryToIgnoreList() ) ); menu->exec( p ); } void OrphanedFilesDialog::showProperties() { new KPropertiesDialog( m_foundFiles->currentFileItem()->url() ); } void OrphanedFilesDialog::openFile() { const KURL fileURL = m_foundFiles->currentFileItem()->url(); const KMimeType::Ptr mime = KMimeType::findByURL( fileURL.url(), 0, true ); /* application shouldn't get passed to KRun since that will execute the * respective file. Instead, we open a "Open with" dialog to let the user * decide (most likely, an editor will be chosen). */ if ( mime->name().startsWith( QString::fromLatin1( "application/" ) ) ) { KFileOpenWithHandler *openWithHandler = new KFileOpenWithHandler; openWithHandler->displayOpenWithDialog( fileURL ); } else { new KRun( fileURL ); } } void OrphanedFilesDialog::deleteFile() { const QString fileName = m_foundFiles->currentFileItem()->url().prettyURL( -1, KURL::StripFileProtocol ); if ( KMessageBox::warningYesNo( this, i18n( "Are you sure you want to erase the " " file %1?" ).arg( fileName ), i18n( "Confirmation" ), KStdGuiItem::yes(), KStdGuiItem::no(), QString::fromLatin1( "showDeleteConfirmationBox" ) ) == KMessageBox::No ) return; const QFileInfo fileInfo( fileName ); if ( !fileInfo.isWritable() ) { KMessageBox::error( this, i18n( "Cannot delete file %1, no write " "access." ).arg( fileName ), i18n( "Cannot delete file" ) ); return; } if ( !QFile::remove( fileName ) ) KMessageBox::error( this, i18n( "Unknown error occured while trying " "to delete file %1!" ).arg( fileName ), i18n( "Couldn't delete file" ) ); return; } void OrphanedFilesDialog::addFileToIgnoreList() { addItemToIgnoreList( m_foundFiles->currentFileItem()->url().prettyURL( -1, KURL::StripFileProtocol ) ); } void OrphanedFilesDialog::addDirectoryToIgnoreList() { addItemToIgnoreList( m_foundFiles->currentFileItem()->url().directory() ); } void OrphanedFilesDialog::addItemToIgnoreList( const QString &item ) { QStringList ignoredFiles = determineIgnoredFiles(); ignoredFiles << item; KConfig *cfg = kapp->config(); cfg->setGroup( QString::fromLatin1( "Orphaned files" ) ); cfg->writeEntry( "Ignored files", ignoredFiles ); cfg->sync(); } void OrphanedFilesDialog::customEvent( QCustomEvent *e ) { if ( e->type() == DirChangeEvent::Id ) { DirChangeEvent *de = static_cast( e ); m_statusText->setText( i18n( "Scanning: %1" ).arg( de->dir() ) ); } else if ( e->type() == FoundFileEvent::Id ) { FoundFileEvent *fe = static_cast( e ); KFileItem *fi = new KFileItem( KFileItem::Unknown, KFileItem::Unknown, KURL( fe->name() ) ); KFileListViewItem *item = new KFileListViewItem( m_foundFiles, fi ); item->setText( 0, fe->name() ); } else if ( e->type() == TraverserDoneEvent::Id ) { m_foundFiles->show(); m_statusText->setText( i18n( "Search finished." ) ); } else if ( e->type() == TraverserListEvent::Id ) { TraverserListEvent *le = static_cast( e ); QPtrList events = le->eventList(); for ( QCustomEvent *event = events.first(); event != 0; event = events.next() ) { customEvent( event ); delete event; } } } #include "orphanedfilesdialog.moc" // vim:ts=4:sw=4:noet:list