kpilot

memofiles.cc

Go to the documentation of this file.
00001 /* memofile-conduit.cc          KPilot
00002 **
00003 ** Copyright (C) 2004-2007 by Jason 'vanRijn' Kasper
00004 **
00005 */
00006 
00007 /*
00008 ** This program is free software; you can redistribute it and/or modify
00009 ** it under the terms of the GNU Lesser General Public License as published by
00010 ** the Free Software Foundation; either version 2.1 of the License, or
00011 ** (at your option) any later version.
00012 **
00013 ** This program is distributed in the hope that it will be useful,
00014 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
00015 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00016 ** GNU Lesser General Public License for more details.
00017 **
00018 ** You should have received a copy of the GNU Lesser General Public License
00019 ** along with this program in a file called COPYING; if not, write to
00020 ** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
00021 ** MA 02110-1301, USA.
00022 */
00023 
00024 /*
00025 ** Bug reports and questions can be sent to kde-pim@kde.org
00026 */
00027 
00028 #include "options.h"
00029 
00030 #include "memofiles.h"
00031 #include "memofile.h"
00032 
00033 QString Memofiles::FIELD_SEP = CSL1("\t");
00034 
00035 Memofiles::Memofiles (MemoCategoryMap & categories, PilotMemoInfo &appInfo, 
00036     QString & baseDirectory, CUDCounter &fCtrPC) :
00037     _categories(categories), _memoAppInfo(appInfo), 
00038     _baseDirectory(baseDirectory), _cudCounter(fCtrPC)
00039 {
00040     FUNCTIONSETUP;
00041     _memofiles.clear();
00042     _memoMetadataFile = _baseDirectory + QDir::separator() + CSL1(".ids");
00043     _categoryMetadataFile = _baseDirectory + QDir::separator() + CSL1(".categories");
00044     _memofiles.setAutoDelete(true);
00045 
00046     _ready = ensureDirectoryReady();
00047 
00048     _metadataLoaded = loadFromMetadata();
00049 }
00050 
00051 Memofiles::~Memofiles()
00052 {
00053     FUNCTIONSETUP;
00054 }
00055 
00056 void Memofiles::load (bool loadAll)
00057 {
00058     FUNCTIONSETUP;
00059 
00060     DEBUGKPILOT << fname
00061     << ": now looking at all memofiles in your directory." << endl;
00062 
00063     // now go through each of our known categories and look in each directory
00064     // for that category for memo files
00065     MemoCategoryMap::ConstIterator it;
00066     int counter = -1;
00067 
00068     for ( it = _categories.begin(); it != _categories.end(); ++it ) {
00069         int category = it.key();
00070         QString categoryName = it.data();
00071         QString categoryDirname = _baseDirectory + QDir::separator() + categoryName;
00072 
00073         QDir dir = QDir(categoryDirname);
00074         if (! dir.exists() ) {
00075             DEBUGKPILOT << fname
00076             << ": category directory: [" << categoryDirname
00077             << "] doesn't exist. skipping." << endl;
00078             continue;
00079         }
00080 
00081 
00082         QStringList entries = dir.entryList(QDir::Files);
00083         QString file;
00084         for(QStringList::Iterator it = entries.begin(); it != entries.end(); ++it) {
00085             file = *it;
00086             QFileInfo info(dir, file);
00087 
00088             if(info.isFile() && info.isReadable()) {
00089 //              DEBUGKPILOT << fname
00090 //              << ": checking category: [" << categoryName
00091 //              << "], file: [" << file << "]." << endl;
00092                 Memofile * memofile = find(categoryName, file);
00093                 if (NULL == memofile) {
00094                     memofile = new Memofile(category, categoryName, file, _baseDirectory);
00095                     memofile->setModified(true);
00096                     _memofiles.append(memofile);
00097                     DEBUGKPILOT << fname
00098                     << ": looks like we didn't know about this one until now. "
00099                     << "created new memofile for category: ["
00100                     << categoryName << "], file: [" << file << "]." << endl;
00101 
00102                 }
00103 
00104                 counter++;
00105 
00106                 // okay, we should have a memofile for this file now.  see if we need
00107                 // to load its text...
00108                 if (memofile->isModified() || loadAll) {
00109                     DEBUGKPILOT << fname
00110                     << ": now loading text for: [" << info.filePath() << "]." << endl;
00111                     memofile->load();
00112                 }
00113             } else {
00114                 DEBUGKPILOT << fname
00115                 << ": couldn't read file: [" << info.filePath() << "]. skipping it." << endl;
00116 
00117             }
00118         } // end of iterating through files in this directory
00119 
00120     } // end of iterating through our categories/directories
00121 
00122     DEBUGKPILOT << fname
00123     << ": looked at: [" << counter << "] files from your directories." << endl;
00124 
00125 
00126     // okay, now we've loaded everything from our directories.  make one last
00127     // pass through our loaded memofiles and see if we need to mark any of them
00128     // as deleted (i.e. we created a memofile object from our metadata, but
00129     // the file is now gone, so it's deleted.
00130     Memofile * memofile;
00131 
00132     for ( memofile = _memofiles.first(); memofile; memofile = _memofiles.next() ) {
00133         if (! memofile->fileExists()) {
00134             memofile->setDeleted( true );
00135         }
00136     }
00137 }
00138 
00145 bool Memofiles::ensureDirectoryReady()
00146 {
00147     FUNCTIONSETUP;
00148 
00149     if (!checkDirectory(_baseDirectory))
00150         return false;
00151 
00152     int failures = 0;
00153     // now make sure that a directory for each category exists.
00154     QString _category_name;
00155     QString dir;
00156 
00157     MemoCategoryMap::Iterator it;
00158     for ( it = _categories.begin(); it != _categories.end(); ++it ) {
00159         _category_name = it.data();
00160         dir = _baseDirectory + QDir::separator() + _category_name;
00161 
00162         DEBUGKPILOT << fname
00163         << ": checking directory: [" << dir     << "]" << endl;
00164 
00165         if (!checkDirectory(dir))
00166             failures++;
00167     }
00168 
00169     return failures == 0;
00170 }
00171 
00172 bool Memofiles::checkDirectory(QString & dir)
00173 {
00174     FUNCTIONSETUP;
00175     // make sure that the directory we're asked to write to exists
00176     QDir d(dir);
00177     QFileInfo fid( dir );
00178 
00179     if ( ! fid.isDir() ) {
00180 
00181         DEBUGKPILOT << fname
00182         << ": directory: [" << dir
00183         << "] doesn't exist. creating...."
00184         << endl;
00185 
00186         if (!d.mkdir(dir)) {
00187 
00188             DEBUGKPILOT << fname
00189             << ": could not create directory: [" << dir
00190             << "].  this won't end well." << endl;
00191             return false;
00192         } else {
00193             DEBUGKPILOT << fname
00194             << ": directory created: ["
00195             << dir << "]." << endl;
00196 
00197         }
00198     } else {
00199         DEBUGKPILOT << fname
00200         << ": directory already existed: ["
00201         << dir << "]." << endl;
00202 
00203     }
00204 
00205     return true;
00206 
00207 }
00208 
00209 void Memofiles::eraseLocalMemos ()
00210 {
00211     FUNCTIONSETUP;
00212 
00213     MemoCategoryMap::Iterator it;
00214     for ( it = _categories.begin(); it != _categories.end(); ++it ) {
00215         QString dir = _baseDirectory + QDir::separator() + it.data();
00216 
00217         if (!folderRemove(QDir(dir))) {
00218             DEBUGKPILOT << fname
00219             << ": couldn't erase all local memos from: ["
00220             << dir << "]." << endl;
00221         }
00222     }
00223     QDir d(_baseDirectory);
00224     d.remove(_memoMetadataFile);
00225 
00226     ensureDirectoryReady();
00227 
00228     _memofiles.clear();
00229 }
00230 
00231 void Memofiles::setPilotMemos (QPtrList<PilotMemo> & memos)
00232 {
00233     FUNCTIONSETUP;
00234 
00235     PilotMemo * memo;
00236 
00237     _memofiles.clear();
00238 
00239     for ( memo = memos.first(); memo; memo = memos.next() ) {
00240         addModifiedMemo(memo);
00241     }
00242 
00243     DEBUGKPILOT << fname
00244     << ": set: ["
00245     << _memofiles.count() << "] from Palm to local." << endl;
00246 
00247 }
00248 
00249 bool Memofiles::loadFromMetadata ()
00250 {
00251     FUNCTIONSETUP;
00252 
00253     _memofiles.clear();
00254 
00255     QFile f( _memoMetadataFile );
00256     if ( !f.open( IO_ReadOnly ) ) {
00257         DEBUGKPILOT << fname
00258         << ": ooh, bad.  couldn't open your memo-id file for reading."
00259         << endl;
00260         return false;
00261     }
00262 
00263     QTextStream t( &f );
00264     Memofile * memofile;
00265 
00266     while ( !t.atEnd() ) {
00267         QString data = t.readLine();
00268         int errors = 0;
00269         bool ok;
00270 
00271         QStringList fields = QStringList::split( FIELD_SEP, data );
00272         if ( fields.count() >= 4 ) {
00273             int id = fields[0].toInt( &ok );
00274             if ( !ok )
00275                 errors++;
00276             int category = fields[1].toInt( &ok );
00277             if ( !ok )
00278                 errors++;
00279             uint lastModified = fields[2].toInt( &ok );
00280             if ( !ok )
00281                 errors++;
00282             uint size = fields[3].toInt( &ok );
00283             if ( !ok )
00284                 errors++;
00285             QString filename = fields[4];
00286             if ( filename.isEmpty() )
00287                 errors++;
00288 
00289             if (errors <= 0) {
00290                 memofile = new Memofile(id, category, lastModified, size,
00291                                         _categories[category], filename, _baseDirectory);
00292                 _memofiles.append(memofile);
00293                 //              DEBUGKPILOT << fname
00294                 //              << ": created memofile from metadata. id: [" << id
00295                 //              << "], category: ["
00296                 //              << _categories[category] << "], filename: [" << filename << "]."
00297                 //              << endl;
00298             }
00299         } else {
00300             errors++;
00301         }
00302 
00303         if (errors > 0) {
00304             DEBUGKPILOT << fname
00305             << ": error: couldn't understand this line: [" << data << "]."
00306             << endl;
00307         }
00308     }
00309 
00310     DEBUGKPILOT << fname
00311     << ": loaded: [" << _memofiles.count() << "] memofiles."
00312     << endl;
00313 
00314     f.close();
00315 
00316     return true;
00317 }
00318 
00319 Memofile * Memofiles::find (recordid_t id)
00320 {
00321 
00322     Memofile * memofile;
00323 
00324     for ( memofile = _memofiles.first(); memofile; memofile = _memofiles.next() ) {
00325         if ( memofile->id() == id) {
00326             return memofile;
00327         }
00328     }
00329 
00330     return NULL;
00331 
00332 }
00333 
00334 Memofile * Memofiles::find (const QString & category, const QString & filename)
00335 {
00336 
00337     Memofile * memofile;
00338 
00339     for ( memofile = _memofiles.first(); memofile; memofile = _memofiles.next() ) {
00340         if ( memofile->getCategoryName() == category &&
00341                 memofile->getFilename() == filename ) {
00342             return memofile;
00343         }
00344     }
00345 
00346     return NULL;
00347 
00348 }
00349 
00350 void Memofiles::deleteMemo(PilotMemo * memo)
00351 {
00352     FUNCTIONSETUP;
00353     if (! memo->isDeleted())
00354         return;
00355 
00356     Memofile * memofile = find(memo->id());
00357     if (memofile) {
00358         memofile->deleteFile();
00359         _memofiles.remove(memofile);
00360         _cudCounter.deleted();
00361     }
00362 }
00363 
00364 
00365 void Memofiles::addModifiedMemo (PilotMemo * memo)
00366 {
00367     FUNCTIONSETUP;
00368 
00369     if (memo->isDeleted()) {
00370         deleteMemo(memo);
00371         return;
00372     }
00373 
00374     QString debug = CSL1(": adding a PilotMemo. id: [")
00375                     + QString::number(memo->id()) + CSL1("], title: [")
00376                     + memo->getTitle() + CSL1("]. ");
00377 
00378     Memofile * memofile = find(memo->id());
00379 
00380     if (NULL == memofile) {
00381         _cudCounter.created();
00382         debug += CSL1(" new from pilot.");
00383     } else {
00384         // we have found a local memofile that was modified on the palm.  for the time
00385         // being (until someone complains, etc.), we will always overwrite changes to
00386         // the local filesystem with changes to the palm (palm overrides local).  at
00387         // some point in the future, we should probably honor a user preference for
00388         // this...
00389         _cudCounter.updated();
00390         _memofiles.remove(memofile);
00391         debug += CSL1(" modified from pilot.");
00392     }
00393 
00394     DEBUGKPILOT << fname
00395     << debug << endl;
00396 
00397     memofile = new Memofile(memo, _categories[memo->category()], filename(memo), _baseDirectory);
00398     memofile->setModifiedByPalm(true);
00399     _memofiles.append(memofile);
00400 
00401 }
00402 
00403 QPtrList<Memofile> Memofiles::getModified ()
00404 {
00405     FUNCTIONSETUP;
00406 
00407     QPtrList<Memofile> modList;
00408     modList.clear();
00409 
00410     Memofile * memofile;
00411 
00412     for ( memofile = _memofiles.first(); memofile; memofile = _memofiles.next() ) {
00413         if ( memofile->isModified() && ! memofile->isModifiedByPalm() ) {
00414             modList.append(memofile);
00415         }
00416     }
00417 
00418     DEBUGKPILOT << fname
00419     << ": found: [" << modList.count() << "] memofiles modified on filesystem." << endl;
00420 
00421     return modList;
00422 }
00423 
00424 void Memofiles::save()
00425 {
00426     FUNCTIONSETUP;
00427 
00428     saveCategoryMetadata();
00429     saveMemos();
00430     // this needs to be done last, because saveMemos() might change
00431     // attributes of the Memofiles
00432     saveMemoMetadata();
00433 
00434 }
00435 
00436 bool Memofiles::saveMemoMetadata()
00437 {
00438     FUNCTIONSETUP;
00439 
00440     DEBUGKPILOT << fname
00441     << ": saving memo metadata to file: ["
00442     << _memoMetadataFile << "]" << endl;
00443 
00444     QFile f( _memoMetadataFile );
00445     QTextStream stream(&f);
00446 
00447     if( !f.open(IO_WriteOnly) ) {
00448         DEBUGKPILOT << fname
00449         << ": ooh, bad.  couldn't open your memo-id file for writing."
00450         << endl;
00451         return false;
00452     }
00453 
00454     Memofile * memofile;
00455 
00456     // each line looks like this, but FIELD_SEP is the separator instead of ","
00457     // id,category,lastModifiedTime,filesize,filename
00458     for ( memofile = _memofiles.first(); memofile; memofile = _memofiles.next() ) {
00459         // don't save deleted memos to our id file
00460         if (! memofile->isDeleted()) {
00461             stream  << memofile->id() << FIELD_SEP
00462             << memofile->category() << FIELD_SEP
00463             << memofile->lastModified() << FIELD_SEP
00464             << memofile->size() << FIELD_SEP
00465             << memofile->filename()
00466             << endl;
00467         }
00468     }
00469 
00470     f.close();
00471 
00472     return true;
00473 
00474 }
00475 
00476 MemoCategoryMap Memofiles::readCategoryMetadata()
00477 {
00478     FUNCTIONSETUP;
00479 
00480     DEBUGKPILOT << fname
00481     << ": reading categories from file: ["
00482     << _categoryMetadataFile << "]" << endl;
00483 
00484     MemoCategoryMap map;
00485     map.clear();
00486 
00487     QFile f( _categoryMetadataFile );
00488     QTextStream stream(&f);
00489 
00490     if( !f.open(IO_ReadOnly) ) {
00491         DEBUGKPILOT << fname
00492         << ": ooh, bad.  couldn't open your categories file for reading."
00493         << endl;
00494         return map;
00495     }
00496 
00497 
00498     while ( !stream.atEnd() ) {
00499         QString data = stream.readLine();
00500         int errors = 0;
00501         bool ok;
00502 
00503         QStringList fields = QStringList::split( FIELD_SEP, data );
00504         if ( fields.count() >= 2 ) {
00505             int id = fields[0].toInt( &ok );
00506             if ( !ok )
00507                 errors++;
00508             QString categoryName = fields[1];
00509             if ( categoryName.isEmpty() )
00510                 errors++;
00511 
00512             if (errors <= 0) {
00513                 map[id] = categoryName;
00514             }
00515         } else {
00516             errors++;
00517         }
00518 
00519         if (errors > 0) {
00520             DEBUGKPILOT << fname
00521             << ": error: couldn't understand this line: [" << data << "]."
00522             << endl;
00523         }
00524     }
00525 
00526     DEBUGKPILOT << fname
00527     << ": loaded: [" << map.count() << "] categories."
00528     << endl;
00529 
00530     f.close();
00531 
00532     return map;
00533 }
00534 
00535 bool Memofiles::saveCategoryMetadata()
00536 {
00537     FUNCTIONSETUP;
00538 
00539 
00540     DEBUGKPILOT << fname
00541     << ": saving categories to file: ["
00542     << _categoryMetadataFile << "]" << endl;
00543 
00544     QFile f( _categoryMetadataFile );
00545     QTextStream stream(&f);
00546 
00547     if( !f.open(IO_WriteOnly) ) {
00548         DEBUGKPILOT << fname
00549         << ": ooh, bad.  couldn't open your categories file for writing."
00550         << endl;
00551         return false;
00552     }
00553 
00554     MemoCategoryMap::Iterator it;
00555     for ( it = _categories.begin(); it != _categories.end(); ++it ) {
00556         stream  << it.key()
00557         << FIELD_SEP
00558         << it.data()
00559         << endl;
00560     }
00561 
00562     f.close();
00563 
00564     return true;
00565 }
00566 
00567 bool Memofiles::saveMemos()
00568 {
00569     FUNCTIONSETUP;
00570 
00571     Memofile * memofile;
00572     bool result = true;
00573 
00574     for ( memofile = _memofiles.first(); memofile; memofile = _memofiles.next() ) {
00575         if (memofile->isDeleted()) {
00576             _memofiles.remove(memofile);
00577         } else {
00578             result = memofile->save();
00579             // Fix prompted by Bug #103922
00580             // if we weren't able to save the file, then remove it from the list.
00581             // if we don't do this, the next sync will think that the user deliberately
00582             // deleted the memofile and will then delete it from the Pilot.
00583             // TODO -- at some point, we should probably tell the user that this
00584             //        did not work, but that will require a String change.
00585             //        Also, this is a partial fix since at this point
00586             //        this memo will never make its way onto the PC, but at least
00587             //        we won't delete it from the Pilot erroneously either.  *sigh*
00588             if (!result) {
00589                 DEBUGKPILOT << fname
00590                     << ": unable to save memofile: ["
00591                     << memofile->filename() 
00592                     << "], now removing it from the metadata list."
00593                     << endl;
00594                 _memofiles.remove(memofile);
00595             }
00596         }
00597     }
00598     return true;
00599 }
00600 
00601 bool Memofiles::isFirstSync()
00602 {
00603     FUNCTIONSETUP;
00604     bool metadataExists = QFile::exists(_memoMetadataFile) &&
00605                           QFile::exists(_categoryMetadataFile);
00606 
00607     bool valid = metadataExists && _metadataLoaded;
00608 
00609     DEBUGKPILOT << fname
00610     << ": local metadata exists: [" << metadataExists
00611     << "], metadata loaded: [" << _metadataLoaded
00612     << "], returning: [" << ! valid << "]" << endl;
00613     return ! valid;
00614 }
00615 
00616 
00617 
00618 bool Memofiles::folderRemove(const QDir &_d)
00619 {
00620     FUNCTIONSETUP;
00621 
00622     QDir d = _d;
00623 
00624     QStringList entries = d.entryList();
00625     for(QStringList::Iterator it = entries.begin(); it != entries.end(); ++it) {
00626         if(*it == CSL1(".") || *it == CSL1(".."))
00627             continue;
00628         QFileInfo info(d, *it);
00629         if(info.isDir()) {
00630             if(!folderRemove(QDir(info.filePath())))
00631                 return FALSE;
00632         } else {
00633             DEBUGKPILOT << fname
00634             << ": deleting file: [" << info.filePath() << "]" << endl;
00635             d.remove(info.filePath());
00636         }
00637     }
00638     QString name = d.dirName();
00639     if(!d.cdUp())
00640         return FALSE;
00641     DEBUGKPILOT << fname
00642     << ": removing folder: [" << name << "]" << endl;
00643     d.rmdir(name);
00644 
00645     return TRUE;
00646 }
00647 
00648 QString Memofiles::filename(PilotMemo * memo)
00649 {
00650     FUNCTIONSETUP;
00651 
00652     QString filename = memo->getTitle();
00653 
00654     if (filename.isEmpty()) {
00655         QString text = memo->text();
00656         int i = text.find(CSL1("\n"));
00657         if (i > 1) {
00658             filename = text.left(i);
00659         }
00660         if (filename.isEmpty()) {
00661             filename = CSL1("empty");
00662         }
00663     }
00664 
00665     filename = sanitizeName(filename);
00666 
00667     QString category = _categories[memo->category()];
00668 
00669     Memofile * memofile = find(category, filename);
00670 
00671     // if we couldn't find a memofile with this filename, or if the
00672     // memofile that is found is the same as the memo that we're looking
00673     // at, then use the filename
00674     if (NULL == memofile || memofile == memo) {
00675         return filename;
00676     }
00677 
00678     int uniq = 2;
00679     QString newfilename;
00680 
00681     // try to find a good filename, but only do this 20 times at the most.
00682     // if our user has 20 memos with the same filename, he/she is asking
00683     // for trouble.
00684     while (NULL != memofile && uniq <=20) {
00685         newfilename = QString(filename + CSL1(".") + QString::number(uniq++) );
00686         memofile = find(category, newfilename);
00687     }
00688 
00689     return newfilename;
00690 }
00691 
00692 QString Memofiles::sanitizeName(QString name)
00693 {
00694     QString clean = name;
00695     // safety net. we can't save a
00696     // filesystem separator as part of a filename, now can we?
00697     clean.replace('/', CSL1("-"));
00698     return clean;
00699 }
00700