1/*
2 This file is part of the kcal library.
3
4 Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org>
5 Copyright (c) 2009 Sergio Martins <iamsergio@gmail.com>
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
16
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
21*/
22
23#include "resourcelocaldir.h"
24#include "resourcelocaldir_p.h"
25#include "calendarlocal.h"
26#include "incidence.h"
27#include "event.h"
28#include "todo.h"
29#include "journal.h"
30
31#include "kresources/configwidget.h"
32
33#include <kcal/assignmentvisitor.h>
34#include <kcal/comparisonvisitor.h>
35#include <kdebug.h>
36#include <klocalizedstring.h>
37#include <kconfig.h>
38#include <kstandarddirs.h>
39#include <kconfiggroup.h>
40
41#include <QtCore/QString>
42#include <QtCore/QDir>
43#include <QtCore/QFileInfo>
44
45#include <typeinfo>
46#include <stdlib.h>
47
48#include "moc_resourcelocaldir.cpp"
49#include "moc_resourcelocaldir_p.cpp"
50
51using namespace KCal;
52
53ResourceLocalDir::ResourceLocalDir()
54 : ResourceCached(), d( new KCal::ResourceLocalDir::Private( this ) )
55{
56 d->init();
57}
58
59ResourceLocalDir::ResourceLocalDir( const KConfigGroup &group )
60 : ResourceCached( group ), d( new KCal::ResourceLocalDir::Private( this ) )
61{
62 readConfig( group );
63 d->init();
64}
65
66ResourceLocalDir::ResourceLocalDir( const QString &dirName )
67 : ResourceCached(), d( new KCal::ResourceLocalDir::Private( dirName, this ) )
68{
69 d->init();
70}
71
72void ResourceLocalDir::readConfig( const KConfigGroup &group )
73{
74 QString url = group.readPathEntry( "CalendarURL", QString() );
75 d->mURL = KUrl( url );
76}
77
78void ResourceLocalDir::writeConfig( KConfigGroup &group )
79{
80 kDebug();
81
82 ResourceCalendar::writeConfig( group );
83
84 group.writePathEntry( "CalendarURL", d->mURL.prettyUrl() );
85}
86
87//@cond PRIVATE
88void ResourceLocalDir::Private::init()
89{
90 mResource->setType( "dir" );
91
92 mResource->setSavePolicy( SaveDelayed );
93
94 connect( &mDirWatch, SIGNAL(dirty(QString)),
95 this, SLOT(updateIncidenceInCalendar(QString)) );
96 connect( &mDirWatch, SIGNAL(created(QString)),
97 this, SLOT(addIncidenceToCalendar(QString)) );
98 connect( &mDirWatch, SIGNAL(deleted(QString)),
99 this, SLOT(deleteIncidenceFromCalendar(QString)) );
100
101 connect ( this, SIGNAL(resourceChanged(ResourceCalendar*)),
102 mResource, SIGNAL(resourceChanged(ResourceCalendar*)) );
103
104 mLock = new KABC::Lock( mURL.path() );
105
106 mDirWatch.addDir( mURL.path(), KDirWatch::WatchFiles );
107 mDirWatch.startScan();
108}
109//@endcond
110
111ResourceLocalDir::~ResourceLocalDir()
112{
113 close();
114
115 delete d->mLock;
116 delete d;
117}
118
119bool ResourceLocalDir::doOpen()
120{
121 QFileInfo dirInfo( d->mURL.path() );
122 return dirInfo.isDir() && dirInfo.isReadable() &&
123 ( dirInfo.isWritable() || readOnly() );
124}
125
126bool ResourceLocalDir::doLoad( bool )
127{
128 kDebug();
129
130 calendar()->close();
131 QString dirName = d->mURL.path();
132
133 if ( !( KStandardDirs::exists( dirName ) || KStandardDirs::exists( dirName + '/' ) ) ) {
134 kDebug() << "Directory '" << dirName << "' doesn't exist yet. Creating it.";
135
136 // Create the directory. Use 0775 to allow group-writable if the umask
137 // allows it (permissions will be 0775 & ~umask). This is desired e.g. for
138 // group-shared directories!
139 return KStandardDirs::makeDir( dirName, 0775 );
140 }
141
142 // The directory exists. Now try to open (the files in) it.
143 kDebug() << dirName;
144 QFileInfo dirInfo( dirName );
145 if ( !( dirInfo.isDir() && dirInfo.isReadable() &&
146 ( dirInfo.isWritable() || readOnly() ) ) ) {
147 return false;
148 }
149
150 QDir dir( dirName );
151 const QStringList entries = dir.entryList( QDir::Files | QDir::Readable );
152
153 bool success = true;
154
155 foreach ( const QString &entry, entries ) {
156 if ( d->isTempFile( entry ) ) {
157 continue; // backup or temporary file, ignore it
158 }
159
160 const QString fileName = dirName + '/' + entry;
161 kDebug() << " read '" << fileName << "'";
162 CalendarLocal cal( calendar()->timeSpec() );
163 if ( !doFileLoad( cal, fileName ) ) {
164 success = false;
165 }
166 }
167
168 return success;
169}
170
171bool ResourceLocalDir::doFileLoad( CalendarLocal &cal, const QString &fileName )
172{
173 return d->doFileLoad( cal, fileName, false );
174}
175
176bool ResourceLocalDir::doSave( bool syncCache )
177{
178 Q_UNUSED( syncCache );
179 Incidence::List list;
180 bool success = true;
181
182 list = addedIncidences();
183 list += changedIncidences();
184
185 for ( Incidence::List::iterator it = list.begin(); it != list.end(); ++it ) {
186 if ( !doSave( *it ) ) {
187 success = false;
188 }
189 }
190
191 return success;
192}
193
194bool ResourceLocalDir::doSave( bool, Incidence *incidence )
195{
196 if ( d->mDeletedIncidences.contains( incidence ) ) {
197 d->mDeletedIncidences.removeAll( incidence );
198 return true;
199 }
200
201 d->mDirWatch.stopScan(); // do prohibit the dirty() signal and a following reload()
202
203 QString fileName = d->mURL.path() + '/' + incidence->uid();
204 kDebug() << "writing '" << fileName << "'";
205
206 CalendarLocal cal( calendar()->timeSpec() );
207 cal.addIncidence( incidence->clone() );
208 const bool ret = cal.save( fileName );
209
210 d->mDirWatch.startScan();
211
212 return ret;
213}
214
215KABC::Lock *ResourceLocalDir::lock()
216{
217 return d->mLock;
218}
219
220void ResourceLocalDir::reload( const QString &file )
221{
222 Q_UNUSED( file );
223}
224
225bool ResourceLocalDir::deleteEvent( Event *event )
226{
227 kDebug();
228 if ( d->deleteIncidenceFile( event ) ) {
229 if ( calendar()->deleteEvent( event ) ) {
230 d->mDeletedIncidences.append( event );
231 return true;
232 } else {
233 return false;
234 }
235 } else {
236 return false;
237 }
238}
239
240void ResourceLocalDir::deleteAllEvents()
241{
242 calendar()->deleteAllEvents();
243}
244
245bool ResourceLocalDir::deleteTodo( Todo *todo )
246{
247 if ( d->deleteIncidenceFile( todo ) ) {
248 if ( calendar()->deleteTodo( todo ) ) {
249 d->mDeletedIncidences.append( todo );
250 return true;
251 } else {
252 return false;
253 }
254 } else {
255 return false;
256 }
257}
258
259void ResourceLocalDir::deleteAllTodos()
260{
261 calendar()->deleteAllTodos();
262}
263
264bool ResourceLocalDir::deleteJournal( Journal *journal )
265{
266 if ( d->deleteIncidenceFile( journal ) ) {
267 if ( calendar()->deleteJournal( journal ) ) {
268 d->mDeletedIncidences.append( journal );
269 return true;
270 } else {
271 return false;
272 }
273 } else {
274 return false;
275 }
276}
277
278void ResourceLocalDir::deleteAllJournals()
279{
280 calendar()->deleteAllJournals();
281}
282
283void ResourceLocalDir::dump() const
284{
285 ResourceCalendar::dump();
286 kDebug() << " Url:" << d->mURL.url();
287}
288
289//@cond PRIVATE
290bool ResourceLocalDir::Private::deleteIncidenceFile( Incidence *incidence )
291{
292 QFile file( mURL.path() + '/' + incidence->uid() );
293 if ( !file.exists() ) {
294 return true;
295 }
296
297 mDirWatch.stopScan();
298 bool removed = file.remove();
299 mDirWatch.startScan();
300 return removed;
301}
302
303bool ResourceLocalDir::Private::isTempFile( const QString &fileName ) const
304{
305 return
306 fileName.contains( QRegExp( "(~|\\.new|\\.tmp)$" ) ) ||
307 QFileInfo( fileName ).fileName().startsWith( QLatin1String( "qt_temp." ) ) ||
308 fileName == mURL.path();
309}
310
311void ResourceLocalDir::Private::addIncidenceToCalendar( const QString &file )
312{
313
314 if ( mResource->isOpen() &&
315 !isTempFile( file ) &&
316 !mResource->calendar()->incidence( getUidFromFileName( file ) ) ) {
317
318 CalendarLocal cal( mResource->calendar()->timeSpec() );
319 if ( doFileLoad( cal, file, true ) ) {
320 emit resourceChanged( mResource );
321 }
322 }
323}
324
325void ResourceLocalDir::Private::updateIncidenceInCalendar( const QString &file )
326{
327 if ( mResource->isOpen() && !isTempFile( file ) ) {
328 CalendarLocal cal( mResource->calendar()->timeSpec() );
329 if ( doFileLoad( cal, file, true ) ) {
330 emit resourceChanged( mResource );
331 }
332 }
333}
334
335QString ResourceLocalDir::Private::getUidFromFileName( const QString &fileName )
336{
337 return QFileInfo( fileName ).fileName();
338}
339
340void ResourceLocalDir::Private::deleteIncidenceFromCalendar( const QString &file )
341{
342
343 if ( mResource->isOpen() && !isTempFile( file ) ) {
344 Incidence *inc = mResource->calendar()->incidence( getUidFromFileName( file ) );
345
346 if ( inc ) {
347 mResource->calendar()->deleteIncidence( inc );
348 emit resourceChanged( mResource );
349 }
350 }
351}
352
353bool ResourceLocalDir::Private::doFileLoad( CalendarLocal &cal,
354 const QString &fileName,
355 const bool replace )
356{
357 if ( !cal.load( fileName ) ) {
358 return false;
359 }
360 Incidence::List incidences = cal.rawIncidences();
361 Incidence::List::ConstIterator it;
362 Incidence *inc;
363 ComparisonVisitor compVisitor;
364 AssignmentVisitor assVisitor;
365 for ( it = incidences.constBegin(); it != incidences.constEnd(); ++it ) {
366 Incidence *i = *it;
367 if ( i ) {
368 // should we replace, and does the incidence exist in calendar?
369 if ( replace && ( inc = mResource->calendar()->incidence( i->uid() ) ) ) {
370 if ( compVisitor.compare( i, inc ) ) {
371 // no need to do anything
372 return false;
373 } else {
374 inc->startUpdates();
375
376 bool assignResult = assVisitor.assign( inc, i );
377
378 if ( assignResult ) {
379 if ( !inc->relatedToUid().isEmpty() ) {
380 QString uid = inc->relatedToUid();
381 inc->setRelatedTo( mResource->calendar()->incidence( uid ) );
382 }
383 inc->updated();
384 inc->endUpdates();
385 } else {
386 inc->endUpdates();
387 kWarning() << "Incidence (uid=" << inc->uid()
388 << ", summary=" << inc->summary()
389 << ") changed type. Replacing it.";
390
391 mResource->calendar()->deleteIncidence( inc );
392 delete inc;
393 mResource->calendar()->addIncidence( i->clone() );
394 }
395 }
396 } else {
397 mResource->calendar()->addIncidence( i->clone() );
398 }
399 }
400 }
401 return true;
402}
403//@endcond
404