kde-playground/kdepim-runtime/resources/shared/singlefileresource.h
2015-04-14 22:08:21 +00:00

365 lines
12 KiB
C++

/*
Copyright (c) 2008 Bertjan Broeksema <broeksema@kde.org>
Copyright (c) 2008 Volker Krause <vkrause@kde.org>
Copyright (c) 2010 David Jarvie <djarvie@kde.org>
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 <akonadi/entitydisplayattribute.h>
#include <kio/job.h>
#include <KDirWatch>
#include <KLocalizedString>
#include <KStandardDirs>
#include <QFile>
#include <QDir>
#include <QPointer>
namespace Akonadi
{
/**
* Base class for single file based resources.
*/
template <typename Settings>
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<bool>() && 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>() ) {
EntityDisplayAttribute *attr = collection.attribute<EntityDisplayAttribute>();
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<EntityDisplayAttribute>( 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<SingleFileResourceConfigDialog<Settings> > dlg
= new SingleFileResourceConfigDialog<Settings>( 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<Settings>* 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<Settings>* dlg )
{
Q_UNUSED(dlg);
}
void retrieveCollections()
{
Collection::List list;
list << rootCollection();
collectionsRetrieved( list );
}
bool readOnly() const
{
return mSettings->readOnly();
}
protected:
Settings *mSettings;
};
}
#endif