singlefileresource.h 10.1 KB
Newer Older
1
/*
2
    Copyright (c) 2008 Bertjan Broeksema <broeksema@kde.org>
3
    Copyright (c) 2008 Volker Krause <vkrause@kde.org>
4
    Copyright (c) 2010 David Jarvie <djarvie@kde.org>
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

    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"
26
#include "singlefileresourceconfigdialog.h"
27

Volker Krause's avatar
Volker Krause committed
28
#include <akonadi/entitydisplayattribute.h>
29

30
#include <kio/job.h>
31
#include <KDirWatch>
32
#include <KLocale>
33
#include <KStandardDirs>
34

35
#include <QFile>
36
#include <QDir>
37
#include <QPointer>
38

39
40
41
42
43
44
45
46
47
48
namespace Akonadi
{

/**
 * Base class for single file based resources.
 */
template <typename Settings>
class SingleFileResource : public SingleFileResourceBase
{
  public:
Bertjan Broeksema's avatar
Bertjan Broeksema committed
49
50
51
    SingleFileResource( const QString &id )
      : SingleFileResourceBase( id )
      , mSettings( new Settings( componentData().config() ) )
52
    {
53
      // The resource needs network when the path refers to a non local file.
Bertjan Broeksema's avatar
Bertjan Broeksema committed
54
      setNeedsNetwork( !KUrl( mSettings->path() ).isLocalFile() );
55
56
57
58
59
60
61
    }

    /**
     * Read changes from the backend file.
     */
    void readFile()
    {
62
63
      if ( KDirWatch::self()->contains( mCurrentUrl.toLocalFile() ) )
        KDirWatch::self()->removeFile( mCurrentUrl.toLocalFile() );
64

Bertjan Broeksema's avatar
Bertjan Broeksema committed
65
      if ( mSettings->path().isEmpty() ) {
66
        emit status( Broken, i18n( "No file selected." ) );
Volker Krause's avatar
Volker Krause committed
67
        cancelTask();
68
69
70
        return;
      }

Bertjan Broeksema's avatar
Bertjan Broeksema committed
71
      mCurrentUrl = KUrl( mSettings->path() );
72
73
74
75
76
77
      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();
      }
78
79
80

      if ( mCurrentUrl.isLocalFile() )
      {
Bertjan Broeksema's avatar
Bertjan Broeksema committed
81
        if ( mSettings->displayName().isEmpty()
82
        && ( name().isEmpty() || name() == identifier() ) && !mCurrentUrl.isEmpty() )
83
          setName( mCurrentUrl.fileName() );
Volker Krause's avatar
Volker Krause committed
84

85
        // check if the file does not exist yet, if so, create it
86
87
        if ( !QFile::exists( mCurrentUrl.toLocalFile() ) ) {
          QFile f( mCurrentUrl.toLocalFile() );
88
89
90
91
92
93
94

          // 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() );
          }

95
          if ( f.open( QIODevice::WriteOnly ) && f.resize( 0 ) ) {
Bertjan Broeksema's avatar
Bertjan Broeksema committed
96
            emit status( Idle, i18nc( "@info:status", "Ready" ) );
97
98
99
          } else {
            emit status( Broken, i18n( "Could not create file '%1'.", mCurrentUrl.prettyUrl() ) );
            mCurrentUrl.clear();
Volker Krause's avatar
Volker Krause committed
100
            cancelTask();
101
102
103
104
            return;
          }
        }

Volker Krause's avatar
Volker Krause committed
105
106
107
108
        // Cache, because readLocalFile will clear mCurrentUrl on failure.
        const QString localFileName = mCurrentUrl.toLocalFile();
        if ( !readLocalFile( mCurrentUrl.toLocalFile() ) ) {
          emit status( Broken, i18n( "Could not read file '%1'", localFileName ) );
Volker Krause's avatar
Volker Krause committed
109
          cancelTask();
Volker Krause's avatar
Volker Krause committed
110
111
          return;
        }
112

Bertjan Broeksema's avatar
Bertjan Broeksema committed
113
        if ( mSettings->monitorFile() )
114
          KDirWatch::self()->addFile( mCurrentUrl.toLocalFile() );
Volker Krause's avatar
Volker Krause committed
115

Bertjan Broeksema's avatar
Bertjan Broeksema committed
116
        emit status( Idle, i18nc( "@info:status", "Ready" ) );
117
      }
118
      else // !mCurrentUrl.isLocalFile()
119
120
121
122
      {
        if ( mDownloadJob )
        {
          emit error( i18n( "Another download is still in progress." ) );
Volker Krause's avatar
Volker Krause committed
123
          cancelTask();
124
125
126
127
128
          return;
        }

        if ( mUploadJob )
        {
129
          emit error( i18n( "Another file upload is still in progress." ) );
Volker Krause's avatar
Volker Krause committed
130
          cancelTask();
131
132
          return;
        }
133

134
        KGlobal::ref();
Volker Krause's avatar
Volker Krause committed
135

136
        // NOTE: Test what happens with remotefile -> save, close before save is finished.
Bertjan Broeksema's avatar
Bertjan Broeksema committed
137
        mDownloadJob = KIO::file_copy( mCurrentUrl, KUrl( cacheFile() ), -1, KIO::Overwrite | KIO::DefaultFlags | KIO::HideProgressInfo );
138
139
        connect( mDownloadJob, SIGNAL( result( KJob * ) ),
                SLOT( slotDownloadJobResult( KJob * ) ) );
Bertjan Broeksema's avatar
Bertjan Broeksema committed
140
141
        connect( mDownloadJob, SIGNAL( percent( KJob *, unsigned long ) ),
                 SLOT( handleProgress( KJob *, unsigned long ) ) );
142
143
144

        emit status( Running, i18n( "Downloading remote file." ) );
      }
145

Bertjan Broeksema's avatar
Bertjan Broeksema committed
146
      const QString display =  mSettings->displayName();
147
148
149
      if ( !display.isEmpty() ) {
        setName( display );
      }
150
151
152
153
154
155
156
    }

    /**
     * Write changes to the backend file.
     */
    void writeFile()
    {
Bertjan Broeksema's avatar
Bertjan Broeksema committed
157
158
      if ( mSettings->readOnly() ) {
        emit error( i18n( "Trying to write to a read-only file: '%1'.", mSettings->path() ) );
Volker Krause's avatar
Volker Krause committed
159
        cancelTask();
160
161
162
163
164
165
166
        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() ) {
        emit status( Broken, i18n( "No file specified." ) );
Volker Krause's avatar
Volker Krause committed
167
        cancelTask();
168
169
170
        return;
      }

171
172
      if ( mCurrentUrl.isLocalFile() ) {
        KDirWatch::self()->stopScan();
173
        const bool writeResult = writeToFile( mCurrentUrl.toLocalFile() );
174
175
        // Update the hash so we can detect at fileChanged() if the file actually
        // did change.
176
        mCurrentHash = calculateHash( mCurrentUrl.toLocalFile() );
177
        saveHash( mCurrentHash );
178
179
        KDirWatch::self()->startScan();
        if ( !writeResult )
Volker Krause's avatar
Volker Krause committed
180
181
        {
          cancelTask();
182
          return;
Volker Krause's avatar
Volker Krause committed
183
        }
Bertjan Broeksema's avatar
Bertjan Broeksema committed
184
        emit status( Idle, i18nc( "@info:status", "Ready" ) );
Volker Krause's avatar
Volker Krause committed
185

186
187
188
189
      } else {
        // Check if there is a download or an upload in progress.
        if ( mDownloadJob ) {
          emit error( i18n( "A download is still in progress." ) );
Volker Krause's avatar
Volker Krause committed
190
          cancelTask();
191
192
193
194
          return;
        }

        if ( mUploadJob ) {
195
          emit error( i18n( "Another file upload is still in progress." ) );
Volker Krause's avatar
Volker Krause committed
196
          cancelTask();
197
198
199
200
201
          return;
        }

        // Write te items to the localy cached file.
        if ( !writeToFile( cacheFile() ) )
Volker Krause's avatar
Volker Krause committed
202
203
        {
          cancelTask();
204
          return;
Volker Krause's avatar
Volker Krause committed
205
        }
206

207
208
209
        // Update the hash so we can detect at fileChanged() if the file actually
        // did change.
        mCurrentHash = calculateHash( cacheFile() );
210
        saveHash( mCurrentHash );
211

212
213
        KGlobal::ref();
        // Start a job to upload the localy cached file to the remote location.
Bertjan Broeksema's avatar
Bertjan Broeksema committed
214
        mUploadJob = KIO::file_copy( KUrl( cacheFile() ), mCurrentUrl, -1, KIO::Overwrite | KIO::DefaultFlags | KIO::HideProgressInfo );
215
216
        connect( mUploadJob, SIGNAL( result( KJob * ) ),
                SLOT( slotUploadJobResult( KJob * ) ) );
Bertjan Broeksema's avatar
Bertjan Broeksema committed
217
218
        connect( mUploadJob, SIGNAL( percent( KJob *, unsigned long ) ),
                 SLOT( handleProgress( KJob *, unsigned long ) ) );
219
220
221

        emit status( Running, i18n( "Uploading cached file to remote location." ) );
      }
Volker Krause's avatar
Volker Krause committed
222
      taskDone();
223
    }
Volker Krause's avatar
Volker Krause committed
224

225
226
227
228
229
230
231
    virtual void collectionChanged( const Collection &collection )
    {
      QString newName;
      if ( collection.hasAttribute<EntityDisplayAttribute>() ) {
        EntityDisplayAttribute *attr = collection.attribute<EntityDisplayAttribute>();
        newName = attr->displayName();
      }
Bertjan Broeksema's avatar
Bertjan Broeksema committed
232
      const QString oldName = mSettings->displayName();
233
      if ( newName != oldName ) {
Bertjan Broeksema's avatar
Bertjan Broeksema committed
234
235
        mSettings->setDisplayName( newName );
        mSettings->writeConfig();
236
237
238
239
      }
      SingleFileResourceBase::collectionChanged( collection );
    }

240
  public Q_SLOTS:
241
242
243
244
245
246
    /**
     * Display the configuration dialog for the resource.
     */
    void configure( WId windowId )
    {
      QPointer<SingleFileResourceConfigDialog<Settings> > dlg
Bertjan Broeksema's avatar
Bertjan Broeksema committed
247
          = new SingleFileResourceConfigDialog<Settings>( windowId, mSettings );
248
249
250
251
      customizeConfigDialog( dlg );
      if ( dlg->exec() == QDialog::Accepted ) {
        if ( dlg ) {   // in case is got destroyed
          configDialogAcceptedActions( dlg );
Bertjan Broeksema's avatar
Bertjan Broeksema committed
252
        }
253
        reloadFile();
Bertjan Broeksema's avatar
Bertjan Broeksema committed
254
        synchronizeCollectionTree();
255
256
257
258
        emit configurationDialogAccepted();
      } else {
        emit configurationDialogRejected();
      }
Laurent Montel's avatar
Laurent Montel committed
259
      delete dlg;
260
261
    }

Volker Krause's avatar
Volker Krause committed
262
  protected:
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
    /**
     * 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);
    }

Volker Krause's avatar
Volker Krause committed
281
282
283
    void retrieveCollections()
    {
      Collection c;
Tobias Koenig's avatar
Tobias Koenig committed
284
      c.setParentCollection( Collection::root() );
Bertjan Broeksema's avatar
Bertjan Broeksema committed
285
286
      c.setRemoteId( mSettings->path() );
      const QString display = mSettings->displayName();
287
      c.setName( display.isEmpty() ? identifier() : display );
Volker Krause's avatar
Volker Krause committed
288
289
      QStringList mimeTypes;
      c.setContentMimeTypes( mSupportedMimetypes );
Bertjan Broeksema's avatar
Bertjan Broeksema committed
290
      if ( mSettings->readOnly() ) {
291
        c.setRights( Collection::CanChangeCollection );
Volker Krause's avatar
Volker Krause committed
292
293
294
295
296
      } else {
        Collection::Rights rights;
        rights |= Collection::CanChangeItem;
        rights |= Collection::CanCreateItem;
        rights |= Collection::CanDeleteItem;
297
        rights |= Collection::CanChangeCollection;
Volker Krause's avatar
Volker Krause committed
298
299
        c.setRights( rights );
      }
Volker Krause's avatar
Volker Krause committed
300
      EntityDisplayAttribute* attr = c.attribute<EntityDisplayAttribute>( Collection::AddIfMissing );
301
302
      attr->setDisplayName( name() );
      attr->setIconName( mCollectionIcon );
Volker Krause's avatar
Volker Krause committed
303
304
305
306
      Collection::List list;
      list << c;
      collectionsRetrieved( list );
    }
Bertjan Broeksema's avatar
Bertjan Broeksema committed
307
308
309

  protected:
    Settings *mSettings;
310
311
312
313
314
};

}

#endif