/* Copyright (c) 2008 Bertjan Broeksema Copyright (c) 2008 Volker Krause Copyright (c) 2010 David Jarvie This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_SINGLEFILERESOURCE_H #define AKONADI_SINGLEFILERESOURCE_H #include "singlefileresourcebase.h" #include "singlefileresourceconfigdialog.h" #include #include #include #include #include #include #include #include namespace Akonadi { /** * Base class for single file based resources. */ template class SingleFileResource : public SingleFileResourceBase { public: SingleFileResource( const QString &id ) : SingleFileResourceBase( id ) , mSettings( new Settings( componentData().config() ) ) { // The resource needs network when the path refers to a non local file. setNeedsNetwork( !KUrl( mSettings->path() ).isLocalFile() ); } ~SingleFileResource() { delete mSettings; } /** * Read changes from the backend file. */ void readFile( bool taskContext = false ) { if ( KDirWatch::self()->contains( mCurrentUrl.toLocalFile() ) ) KDirWatch::self()->removeFile( mCurrentUrl.toLocalFile() ); if ( mSettings->path().isEmpty() ) { const QString message = i18n( "No file selected." ); kWarning() << message; emit status( NotConfigured, i18n("The resource not configured yet") ); if ( taskContext ) cancelTask(); return; } mCurrentUrl = KUrl( mSettings->path() ); if ( mCurrentHash.isEmpty() ) { // First call to readFile() lets see if there is a hash stored in a // cache file. If both are the same than there is no need to load the // file and synchronize the resource. mCurrentHash = loadHash(); } if ( mCurrentUrl.isLocalFile() ) { if ( mSettings->displayName().isEmpty() && ( name().isEmpty() || name() == identifier() ) && !mCurrentUrl.isEmpty() ) setName( mCurrentUrl.fileName() ); // check if the file does not exist yet, if so, create it if ( !QFile::exists( mCurrentUrl.toLocalFile() ) ) { QFile f( mCurrentUrl.toLocalFile() ); // first create try to create the directory the file should be located in QDir dir = QFileInfo(f).dir(); if ( ! dir.exists() ) { dir.mkpath( dir.path() ); } if ( f.open( QIODevice::WriteOnly ) && f.resize( 0 ) ) { emit status( Idle, i18nc( "@info:status", "Ready" ) ); } else { const QString message = i18n( "Could not create file '%1'.", mCurrentUrl.prettyUrl() ); kWarning() << message; emit status( Broken, message ); mCurrentUrl.clear(); if ( taskContext ) cancelTask(); return; } } // Cache, because readLocalFile will clear mCurrentUrl on failure. const QString localFileName = mCurrentUrl.toLocalFile(); if ( !readLocalFile( mCurrentUrl.toLocalFile() ) ) { const QString message = i18n( "Could not read file '%1'", localFileName ); kWarning() << message; emit status( Broken, message ); if ( taskContext ) cancelTask(); return; } if ( mSettings->monitorFile() ) KDirWatch::self()->addFile( mCurrentUrl.toLocalFile() ); emit status( Idle, i18nc( "@info:status", "Ready" ) ); } else // !mCurrentUrl.isLocalFile() { if ( mDownloadJob ) { const QString message = i18n( "Another download is still in progress." ); kWarning() << message; emit error( message ); if ( taskContext ) cancelTask(); return; } if ( mUploadJob ) { const QString message = i18n( "Another file upload is still in progress." ); kWarning() << message; emit error( message ); if ( taskContext ) cancelTask(); return; } KGlobal::ref(); // NOTE: Test what happens with remotefile -> save, close before save is finished. mDownloadJob = KIO::file_copy( mCurrentUrl, KUrl( cacheFile() ), -1, KIO::Overwrite | KIO::DefaultFlags | KIO::HideProgressInfo ); connect( mDownloadJob, SIGNAL(result(KJob*)), SLOT(slotDownloadJobResult(KJob*)) ); connect( mDownloadJob, SIGNAL(percent(KJob*,ulong)), SLOT(handleProgress(KJob*,ulong)) ); emit status( Running, i18n( "Downloading remote file." ) ); } const QString display = mSettings->displayName(); if ( !display.isEmpty() ) { setName( display ); } } void writeFile( const QVariant &task_context ) { writeFile( task_context.canConvert() && task_context.toBool() ); } /** * Write changes to the backend file. */ void writeFile( bool taskContext = false ) { if ( mSettings->readOnly() ) { const QString message = i18n( "Trying to write to a read-only file: '%1'.", mSettings->path() ); kWarning() << message; emit error( message ); if ( taskContext ) cancelTask(); return; } // We don't use the Settings::self()->path() here as that might have changed // and in that case it would probably cause data lose. if ( mCurrentUrl.isEmpty() ) { const QString message = i18n( "No file specified." ); kWarning() << message; emit status( Broken, message ); if ( taskContext ) cancelTask(); return; } if ( mCurrentUrl.isLocalFile() ) { KDirWatch::self()->stopScan(); const bool writeResult = writeToFile( mCurrentUrl.toLocalFile() ); // Update the hash so we can detect at fileChanged() if the file actually // did change. mCurrentHash = calculateHash( mCurrentUrl.toLocalFile() ); saveHash( mCurrentHash ); KDirWatch::self()->startScan(); if ( !writeResult ) { kWarning() << "Error writing to file..."; if ( taskContext ) cancelTask(); return; } emit status( Idle, i18nc( "@info:status", "Ready" ) ); } else { // Check if there is a download or an upload in progress. if ( mDownloadJob ) { const QString message = i18n( "A download is still in progress." ); kWarning() << message; emit error( message ); if ( taskContext ) cancelTask(); return; } if ( mUploadJob ) { const QString message = i18n( "Another file upload is still in progress." ); kWarning() << message; emit error( message ); if ( taskContext ) cancelTask(); return; } // Write te items to the locally cached file. if ( !writeToFile( cacheFile() ) ) { kWarning() << "Error writing to file"; if ( taskContext ) cancelTask(); return; } // Update the hash so we can detect at fileChanged() if the file actually // did change. mCurrentHash = calculateHash( cacheFile() ); saveHash( mCurrentHash ); KGlobal::ref(); // Start a job to upload the locally cached file to the remote location. mUploadJob = KIO::file_copy( KUrl( cacheFile() ), mCurrentUrl, -1, KIO::Overwrite | KIO::DefaultFlags | KIO::HideProgressInfo ); connect( mUploadJob, SIGNAL(result(KJob*)), SLOT(slotUploadJobResult(KJob*)) ); connect( mUploadJob, SIGNAL(percent(KJob*,ulong)), SLOT(handleProgress(KJob*,ulong)) ); emit status( Running, i18n( "Uploading cached file to remote location." ) ); } if ( taskContext ) taskDone(); } virtual void collectionChanged( const Collection &collection ) { QString newName; if ( collection.hasAttribute() ) { EntityDisplayAttribute *attr = collection.attribute(); newName = attr->displayName(); } const QString oldName = mSettings->displayName(); if ( newName != oldName ) { mSettings->setDisplayName( newName ); mSettings->writeConfig(); } SingleFileResourceBase::collectionChanged( collection ); } virtual Collection rootCollection() const { Collection c; c.setParentCollection( Collection::root() ); c.setRemoteId( mSettings->path() ); const QString display = mSettings->displayName(); c.setName( display.isEmpty() ? identifier() : display ); QStringList mimeTypes; c.setContentMimeTypes( mSupportedMimetypes ); if ( readOnly() ) { c.setRights( Collection::CanChangeCollection ); } else { Collection::Rights rights; rights |= Collection::CanChangeItem; rights |= Collection::CanCreateItem; rights |= Collection::CanDeleteItem; rights |= Collection::CanChangeCollection; c.setRights( rights ); } EntityDisplayAttribute* attr = c.attribute( Collection::AddIfMissing ); attr->setDisplayName( name() ); attr->setIconName( mCollectionIcon ); return c; } public Q_SLOTS: /** * Display the configuration dialog for the resource. */ void configure( WId windowId ) { QPointer > dlg = new SingleFileResourceConfigDialog( windowId, mSettings ); customizeConfigDialog( dlg ); if ( dlg->exec() == QDialog::Accepted ) { if ( dlg ) { // in case is got destroyed configDialogAcceptedActions( dlg ); } reloadFile(); synchronizeCollectionTree(); emit configurationDialogAccepted(); } else { emit configurationDialogRejected(); } delete dlg; } protected: /** * Implement in derived classes to customize the configuration dialog * before it is displayed. */ virtual void customizeConfigDialog( SingleFileResourceConfigDialog* dlg ) { Q_UNUSED(dlg); } /** * Implement in derived classes to do things when the configuration dialog * has been accepted, before reloadFile() is called. */ virtual void configDialogAcceptedActions( SingleFileResourceConfigDialog* dlg ) { Q_UNUSED(dlg); } void retrieveCollections() { Collection::List list; list << rootCollection(); collectionsRetrieved( list ); } bool readOnly() const { return mSettings->readOnly(); } protected: Settings *mSettings; }; } #endif