/* KPilot ** ** Copyright (C) 2001 by Dan Pilone ** Copyright (C) 2002,2003,2004 by Adriaan de Groot ** ** This file defines the SyncAction for the knotes-conduit plugin. */ /* ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program 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 General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program in a file called COPYING; if not, write to ** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, ** MA 02110-1301, USA. */ /* ** Bug reports and questions can be sent to kde-pim@kde.org */ #include "options.h" #include #include #include #include #include #include #include //#include #include // required by pilot-link includes #include #include "pilotMemo.h" #include "pilotSerialDatabase.h" //#include "KNotesIface_stub.h" #include "knotes-factory.h" #include "knotes-action.moc" #include "knotesconduitSettings.h" extern "C" { unsigned long version_conduit_knotes = Pilot::PLUGIN_API; } typedef TQString KNoteID_t; typedef const TQString &KNoteID_pt; class NoteAndMemo { public: NoteAndMemo() : noteId(),memoId(-1) { } ; NoteAndMemo(KNoteID_pt noteid,int memoid) : noteId(noteid),memoId(memoid) { } ; bool operator ==(const NoteAndMemo &p) const { return (p.memo()==memoId) && (p.note()==noteId); } int memo() const { return memoId; } ; KNoteID_t note() const { return noteId; } ; inline bool valid() const { return (memoId>0) && (!noteId.isEmpty()) ; } ; TQString toString() const { return CSL1("<%1,%2>").arg(noteId).arg(memoId); } ; static NoteAndMemo findNote(const TQValueList &,KNoteID_pt note); static NoteAndMemo findMemo(const TQValueList &,int memo); protected: KNoteID_t noteId; int memoId; } ; NoteAndMemo NoteAndMemo::findNote(const TQValueList &l ,KNoteID_pt note) { FUNCTIONSETUP; for (TQValueList::ConstIterator it = l.begin(); it != l.end(); ++it) { if ((*it).note()==note) return *it; } return NoteAndMemo(); } NoteAndMemo NoteAndMemo::findMemo(const TQValueList &l , int memo) { FUNCTIONSETUP; for (TQValueList::ConstIterator it =l.begin(); it != l.end(); ++it) { if ((*it).memo()==memo) return *it; } return NoteAndMemo(); } class KNotesAction::KNotesActionPrivate { public: KNotesActionPrivate() : fNotesResource(0L), fTimer(0L), fDeleteCounter(0), fModifiedNotesCounter(0), fModifiedMemosCounter(0), fAddedNotesCounter(0), fAddedMemosCounter(0), fDeletedNotesCounter(0), fDeletedMemosCounter(0), fDeleteNoteForMemo(false) { } ; ~KNotesActionPrivate() { fNotesResource->save(); KPILOT_DELETE(fNotesResource); KPILOT_DELETE(fTimer); } // The record index we're dealing with. Used by // CopyHHToPC sync only. int fRecordIndex; KCal::CalendarLocal *fNotesResource; // This is the collection of notes held by KNotes and KCal::Journal::List fNotes; // This iterates through that list; it's in here because // we use slots to process one item at a time and need // to keep track of where we are between slot calls. KCal::Journal::List::ConstIterator fIndex; // The DCOP client for this application, and the KNotes stub. // DCOPClient *fDCOP; //KNotesIface_stub *fKNotes; // The timer for invoking process() to do some more work. TQTimer *fTimer; // The database we're working with (MemoDB) // PilotSerialDatabase *fDatabase; // Some counter that needs to be preserved between calls to // process(). Typically used to note how much work is done. int fDeleteCounter; // Count deleted memos as well. unsigned int fModifiedNotesCounter; // Count modified KNotes. unsigned int fModifiedMemosCounter; unsigned int fAddedNotesCounter; unsigned int fAddedMemosCounter; unsigned int fDeletedNotesCounter; unsigned int fDeletedMemosCounter; // We need to translate between the ids that KNotes uses and // Pilot id's, so we make a list of pairs. // TQValueList fIdList; // Setting to delete a KNote when the corresponding memo // has been deleted. bool fDeleteNoteForMemo; }; KNotesAction::KNotesAction(KPilotLink *o, const char *n, const TQStringList &a) : ConduitAction(o,n ? n : "knotes-conduit",a), fP(new KNotesActionPrivate) { FUNCTIONSETUP; /* if (fP) fP->fDCOP = TDEApplication::kApplication()->dcopClient(); if (fP && !fP->fDCOP) { WARNINGKPILOT << "Can't get DCOP client." << endl; } */ } /* virtual */ KNotesAction::~KNotesAction() { FUNCTIONSETUP; KPILOT_DELETE(fP); } /* virtual */ bool KNotesAction::exec() { FUNCTIONSETUP; DEBUGKPILOT << fname << ": Starting knotes conduit." << endl; if (syncMode().isTest()) { test(); delayDone(); return true; } TQString e; if (!openKNotesResource()) return false; // Database names seem to be latin1 if (!openDatabases(CSL1("MemoDB"))) { #ifdef DEBUG DEBUGKPILOT << fname << "Can not open databases." << endl; #endif emit logError(i18n("Could not open MemoDB on the handheld.")); return false; } fP->fTimer = new TQTimer(this); fActionStatus = Init; // this is not needed. As it is done in the initstate in process(); // resetIndexes(); connect(fP->fTimer,TQ_SIGNAL(timeout()),TQ_SLOT(process())); fP->fTimer->start(0,false); return true; } void KNotesAction::test() { if (!openKNotesResource()) return; listNotes(); } bool KNotesAction::openKNotesResource() { FUNCTIONSETUP; TDEConfig korgcfg( locate( "config", CSL1("korganizerrc") ) ); korgcfg.setGroup( "Time & Date" ); TQString tz(korgcfg.readEntry( "TimeZoneId" ) ); fP->fNotesResource = new KCal::CalendarLocal(tz); KURL mURL = TDEGlobal::dirs()->saveLocation( "data", "knotes/" ) + "notes.ics"; if( fP->fNotesResource->load( mURL.path() ) ) { fP->fNotes = fP->fNotesResource->journals(); return true; } else { emit logError( i18n("Could not load the resource at: %1").arg(mURL.path()) ); return false; } } void KNotesAction::resetIndexes() { FUNCTIONSETUP; fP->fRecordIndex = 0; fP->fIndex = fP->fNotes.begin(); } void KNotesAction::listNotes() { FUNCTIONSETUP; KCal::Journal::List notes = fP->fNotesResource->journals(); DEBUGKPILOT << fname << ": the resource contains " << notes.size() << " note(s)." << endl; KCal::Journal::List::ConstIterator it; int i = 1; for ( it = notes.begin(); it != notes.end(); ++it ) { DEBUGKPILOT << fname << ": note " << i << " has id " << (*it)->uid() << endl; i++; } DEBUGKPILOT << fname << ": " << "Sync direction: " << syncMode().name() << endl; } /* slot */ void KNotesAction::process() { FUNCTIONSETUP; DEBUGKPILOT << fname << ": Now in state " << fActionStatus << endl; switch(fActionStatus) { case Init: resetIndexes(); getAppInfo(); getConfigInfo(); switch(syncMode().mode()) { case SyncAction::SyncMode::eBackup: case SyncAction::SyncMode::eRestore: // Impossible! fActionStatus = Done; break; case SyncAction::SyncMode::eCopyHHToPC : listNotes(); // Debugging fActionStatus = MemosToKNotes; break; case SyncAction::SyncMode::eHotSync: case SyncAction::SyncMode::eFullSync: case SyncAction::SyncMode::eCopyPCToHH: fActionStatus = ModifiedNotesToPilot; break; } break; case ModifiedNotesToPilot: if (modifyNoteOnPilot()) { resetIndexes(); fActionStatus = DeleteNotesOnPilot; } break; case DeleteNotesOnPilot: if (deleteNoteOnPilot()) { resetIndexes(); fActionStatus = NewNotesToPilot; } break; case NewNotesToPilot : if (addNewNoteToPilot()) { resetIndexes(); fDatabase->resetDBIndex(); switch(syncMode().mode()) { case SyncAction::SyncMode::eBackup: case SyncAction::SyncMode::eRestore: case SyncAction::SyncMode::eCopyHHToPC : // Impossible! fActionStatus = Done; break; case SyncAction::SyncMode::eHotSync: case SyncAction::SyncMode::eFullSync: fActionStatus = MemosToKNotes; break; case SyncAction::SyncMode::eCopyPCToHH: fActionStatus = Cleanup; break; } } break; case MemosToKNotes : if (syncMemoToKNotes()) { fActionStatus=Cleanup; } break; case Cleanup : cleanupMemos(); break; default : if (fP->fTimer) fP->fTimer->stop(); delayDone(); } } void KNotesAction::getConfigInfo() { FUNCTIONSETUP; KNotesConduitSettings::self()->readConfig(); fP->fDeleteNoteForMemo = KNotesConduitSettings::deleteNoteForMemo(); TQValueList notes; TQValueList memos; // Make this match the type of KNoteID_t ! notes=KNotesConduitSettings::noteIds(); memos=KNotesConduitSettings::memoIds(); if (notes.count() != memos.count()) { WARNINGKPILOT << ": Notes and memo id lists don't match (" << notes.count() << "," << memos.count() << ")" << endl; notes.clear(); memos.clear(); setFirstSync( true ); } TQValueList::ConstIterator iNotes = notes.begin(); TQValueList::ConstIterator iMemos = memos.begin(); while((iNotes != notes.end()) && (iMemos != memos.end())) { fP->fIdList.append(NoteAndMemo(*iNotes,*iMemos)); ++iNotes; ++iMemos; } } void KNotesAction::getAppInfo() { FUNCTIONSETUP; resetIndexes(); } bool KNotesAction::modifyNoteOnPilot() { FUNCTIONSETUP; return true; /* if (fP->fIndex == fP->fNotes.end()) { return true; } */ //TODO DCOP_REMOVAL /* if (fP->fKNotes->isModified(CSL1("kpilot"),fP->fIndex.key())) { #ifdef DEBUG DEBUGKPILOT << fname << ": The note #" << fP->fIndex.key() << " with name " << fP->fIndex.data() << " is modified in KNotes." << endl; #endif NoteAndMemo nm = NoteAndMemo::findNote(fP->fIdList, fP->fIndex.key()); if (nm.valid()) { TQString text,title,body; title = fP->fIndex.data(); body = fP->fKNotes->text(fP->fIndex.key()); if (body.startsWith(title)) { text = body; } else { text = title + CSL1("\n") + body; } PilotMemo *a = new PilotMemo(text); PilotRecord *r = a->pack(); r->setID(nm.memo()); int newid = fDatabase->writeRecord(r); fLocalDatabase->writeRecord(r); if (newid != nm.memo()) { WARNINGKPILOT << ": Memo id changed during write? " << "From " << nm.memo() << " to " << newid << endl; } } else { WARNINGKPILOT << "Modified note unknown to Pilot" << endl; // Add it anyway, with new PilotID. int newid = addNoteToPilot(); fP->fIdList.remove(nm); fP->fIdList.append(NoteAndMemo(fP->fIndex.key(),newid)); } ++(fP->fModifiedMemosCounter); } */ //++(fP->fIndex); //return false; } bool KNotesAction::deleteNoteOnPilot() { FUNCTIONSETUP; /* TQValueList::Iterator i = fP->fIdList.begin(); while ( i != fP->fIdList.end() ) { // TODO DCOP_REMOVE if (fP->fNotes.contains((*i).note())) { #ifdef DEBUG DEBUGKPILOT << fname << ": Note " << (*i).note() << " still exists." << endl; #endif } else { #ifdef DEBUG DEBUGKPILOT << fname << ": Note " << (*i).note() << " is deleted." << endl; #endif fDatabase->deleteRecord((*i).memo()); fLocalDatabase->deleteRecord((*i).memo()); i = fP->fIdList.remove(i); fP->fDeletedMemosCounter++; continue; } ++i; } */ return true; } bool KNotesAction::addNewNoteToPilot() { FUNCTIONSETUP; if (fP->fIndex == fP->fNotes.end()) { return true; } KCal::Journal *j = (*fP->fIndex); if( j->pilotId() == 0 ) { DEBUGKPILOT << fname << ": Adding note with id " << j->uid() << " to pilot." << endl; int newid = addNoteToPilot(); ++(fP->fAddedMemosCounter); } //TODO DCOP_REMOVAL /* if (fP->fKNotes->isNew(CSL1("kpilot"),fP->fIndex.key())) { int newid = addNoteToPilot(); fP->fIdList.append(NoteAndMemo(fP->fIndex.key(),newid)); ++(fP->fAddedMemosCounter); } */ ++(fP->fIndex); return false; } bool KNotesAction::syncMemoToKNotes() { FUNCTIONSETUP; PilotRecord *rec = 0L; if ( syncMode() == SyncAction::SyncMode::eCopyHHToPC ) { #ifdef DEBUG DEBUGKPILOT << fname << ": Read record " << fP->fRecordIndex << endl; #endif rec = fDatabase->readRecordByIndex(fP->fRecordIndex); fP->fRecordIndex++; } else { rec = fDatabase->readNextModifiedRec(); } if (!rec) { return true; } PilotMemo *memo = new PilotMemo(rec); NoteAndMemo m = NoteAndMemo::findMemo(fP->fIdList,memo->id()); #ifdef DEBUG DEBUGKPILOT << fname << ": Looking at memo " << memo->id() << " which was found " << m.toString() << endl; #endif if (memo->isDeleted()) { #ifdef DEBUG DEBUGKPILOT << fname << ": It's been deleted." << endl; #endif if (m.valid()) { // We knew about the note already, but it // has changed on the Pilot. // // if (fP->fDeleteNoteForMemo) { //TODO DCOP_REMOVAL //fP->fKNotes->killNote(m.note(),KNotesConduitSettings::suppressKNotesConfirm() //) ; fP->fDeletedNotesCounter++; } } else { #ifdef DEBUG DEBUGKPILOT << fname << ": It's new and deleted." << endl; #endif } fLocalDatabase->deleteRecord(rec->id()); } else { if (m.valid()) { #ifdef DEBUG DEBUGKPILOT << fname << ": It's just modified." << endl; DEBUGKPILOT << fname << ": <" // << fP->fNotes[m.note()] << "> <" << memo->shortTitle() << ">" << endl; #endif // Check if KNotes still knows about this note //TODO DCOP_REMOVAL /* if (!(fP->fKNotes->name(m.note()).isEmpty())) { updateNote(m,memo); } else { uint c = fP->fIdList.remove(m); if (!c) { WARNINGKPILOT << "Tried to remove valid note and failed." << endl; } addMemoToKNotes(memo); } */ } else { addMemoToKNotes(memo); } fLocalDatabase->writeRecord(rec); } KPILOT_DELETE(memo); KPILOT_DELETE(rec); return false; } void KNotesAction::updateNote(const NoteAndMemo &m, const PilotMemo *memo) { FUNCTIONSETUP; //TODO DCOP_REMOVAL if (true/*fP->fNotes[m.note()] != memo->shortTitle()*/) { // Name changed. KNotes might complain though. //TODO DCOP_REMOVAL //fP->fKNotes->setName(m.note(), memo->shortTitle()); } //TODO DCOP_REMOVAL //fP->fKNotes->setText(m.note(),memo->text()); fP->fModifiedNotesCounter++; } void KNotesAction::addMemoToKNotes(const PilotMemo *memo) { FUNCTIONSETUP; // This note is new to KNotes //TODO DCOP_REMOVAL //KNoteID_t i = fP->fKNotes->newNote(memo->shortTitle(), memo->text()); //fP->fIdList.append(NoteAndMemo(i,memo->id())); //fP->fAddedNotesCounter++; #ifdef DEBUG //TODO DCOP_REMOVAL //DEBUGKPILOT << fname << ": It's new with knote id " << i << endl; #endif } int KNotesAction::addNoteToPilot() { FUNCTIONSETUP; KCal::Journal *j = (*fP->fIndex); #ifdef DEBUG DEBUGKPILOT << fname << ": The note #" << j->uid() << " with name " << j->summary() << " is new to the Pilot." << endl; #endif TQString text = j->summary() + CSL1("\n"); text.append( j->description() ); //TODO DCOP_REMOVAL //text.append(fP->fKNotes->text(fP->fIndex.key())); PilotMemo *a = new PilotMemo(text); PilotRecord *r = a->pack(); int newid = fDatabase->writeRecord(r); fLocalDatabase->writeRecord(r); j->setPilotId( newid ); delete r; delete a; delete j; fP->fAddedMemosCounter++; return newid; } void KNotesAction::cleanupMemos() { FUNCTIONSETUP; // Tell KNotes we're up-to-date //TODO DCOP_REMOVAL //fP->fKNotes->sync(CSL1("kpilot")); #ifdef DEBUG DEBUGKPILOT << fname << ": Writing " << fP->fIdList.count() << " pairs to the config file." << endl; DEBUGKPILOT << fname << ": The config file is read-only: " << KNotesConduitSettings::self()->config()->isReadOnly() << endl; #endif TQValueList notes; TQValueList memos; for (TQValueList::ConstIterator i = fP->fIdList.begin(); i!=fP->fIdList.end(); ++i) { notes.append((*i).note()); memos.append((*i).memo()); } KNotesConduitSettings::setNoteIds(notes); KNotesConduitSettings::setMemoIds(memos); KNotesConduitSettings::self()->writeConfig(); fActionStatus=Done; fDatabase->cleanup(); fDatabase->resetSyncFlags(); fLocalDatabase->cleanup(); fLocalDatabase->resetSyncFlags(); // Tell the user what happened. If no changes were // made, spoke remains false and we'll tack a // message on to the end saying so, so that // the user always gets at least one message. bool spoke = false; if (fP->fAddedMemosCounter) { addSyncLogEntry(i18n("Added one new memo.", "Added %n new memos.", fP->fAddedMemosCounter)); } if (fP->fModifiedMemosCounter) { addSyncLogEntry(i18n("Modified one memo.", "Modified %n memos.", fP->fModifiedMemosCounter)); spoke = true; } if (fP->fDeletedMemosCounter) { addSyncLogEntry(i18n("Deleted one memo.", "Deleted %n memos.",fP->fDeletedMemosCounter)); spoke = true; } if (fP->fAddedNotesCounter) { addSyncLogEntry(i18n("Added one note to KNotes.", "Added %n notes to KNotes.",fP->fAddedNotesCounter)); spoke = true; } if (fP->fModifiedNotesCounter) { addSyncLogEntry(i18n("Modified one note in KNotes.", "Modified %n notes in KNotes.",fP->fModifiedNotesCounter)); spoke = true; } if (fP->fDeletedNotesCounter) { addSyncLogEntry(i18n("Deleted one note from KNotes.", "Deleted %n notes from KNotes.",fP->fDeletedNotesCounter)); spoke = true; } if (!spoke) { addSyncLogEntry(i18n("No change to KNotes.")); } } /* virtual */ TQString KNotesAction::statusString() const { switch(fActionStatus) { case Init : return CSL1("Init"); case NewNotesToPilot : return CSL1("NewNotesToPilot key=%1"); // TODO DCOP_REMOVAL .arg(fP->fIndex.key()); case ModifiedNotesToPilot : return CSL1("ModifiedNotesToPilot key=%1"); //TODO DCOP_REMOVAL .arg(fP->fIndex.key()); case MemosToKNotes : return CSL1("MemosToKNotes rec=%1") .arg(fP->fRecordIndex); case Cleanup : return CSL1("Cleanup"); case Done : return CSL1("Done"); default : return CSL1("Unknown (%1)").arg(fActionStatus); } }