diff options
Diffstat (limited to 'lib/plugin.cc')
-rw-r--r-- | lib/plugin.cc | 760 |
1 files changed, 760 insertions, 0 deletions
diff --git a/lib/plugin.cc b/lib/plugin.cc new file mode 100644 index 0000000..27631ce --- /dev/null +++ b/lib/plugin.cc @@ -0,0 +1,760 @@ +/* KPilot +** +** Copyright (C) 2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** +** This file defines the base class of all KPilot conduit plugins configuration +** dialogs. This is necessary so that we have a fixed API to talk to from +** inside KPilot. +** +** The factories used by KPilot plugins are also documented here. +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser 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 <stdlib.h> + +#include <tqdir.h> +#include <tqfileinfo.h> +#include <tqhbox.h> +#include <tqlabel.h> +#include <tqlayout.h> +#include <tqpushbutton.h> +#include <tqregexp.h> +#include <tqstringlist.h> +#include <tqtabwidget.h> +#include <tqtextview.h> +#include <tqtimer.h> + +#include <dcopclient.h> +#include <kaboutapplication.h> +#include <kactivelabel.h> +#include <kapplication.h> +#include <kglobal.h> +#include <kiconloader.h> +#include <kinstance.h> +#include <klibloader.h> +#include <kmessagebox.h> +#include <kservice.h> +#include <kservicetype.h> +#include <kstandarddirs.h> + +#include "pilotSerialDatabase.h" +#include "pilotLocalDatabase.h" + +#include "plugin.moc" + +ConduitConfigBase::ConduitConfigBase(TQWidget *parent, + const char *name) : + TQObject(parent,name), + fModified(false), + fWidget(0L), + fConduitName(i18n("Unnamed")) +{ + FUNCTIONSETUP; +} + +ConduitConfigBase::~ConduitConfigBase() +{ + FUNCTIONSETUP; +} + +/* slot */ void ConduitConfigBase::modified() +{ + fModified=true; + emit changed(true); +} + +/* virtual */ TQString ConduitConfigBase::maybeSaveText() const +{ + FUNCTIONSETUP; + + return i18n("<qt>The <i>%1</i> conduit's settings have been changed. Do you " + "want to save the changes before continuing?</qt>").arg(this->conduitName()); +} + +/* virtual */ bool ConduitConfigBase::maybeSave() +{ + FUNCTIONSETUP; + + if (!isModified()) return true; + + int r = KMessageBox::questionYesNoCancel(fWidget, + maybeSaveText(), + i18n("%1 Conduit").arg(this->conduitName()), KStdGuiItem::save(), KStdGuiItem::discard()); + if (r == KMessageBox::Cancel) return false; + if (r == KMessageBox::Yes) commit(); + return true; +} + +TQWidget *ConduitConfigBase::aboutPage(TQWidget *parent, KAboutData *ad) +{ + FUNCTIONSETUP; + + TQWidget *w = new TQWidget(parent, "aboutpage"); + + TQString s; + TQLabel *text; + KIconLoader *l = KGlobal::iconLoader(); + const KAboutData *p = ad ? ad : KGlobal::instance()->aboutData(); + + TQGridLayout *grid = new TQGridLayout(w, 5, 4, SPACING); + + grid->addColSpacing(0, SPACING); + grid->addColSpacing(4, SPACING); + + + TQPixmap applicationIcon = + l->loadIcon(TQString::tqfromLatin1(p->appName()), + KIcon::Desktop, + 64, KIcon::DefaultState, 0L, + true); + + if (applicationIcon.isNull()) + { + applicationIcon = l->loadIcon(TQString::tqfromLatin1("kpilot"), + KIcon::Desktop); + } + + text = new TQLabel(w); + // Experiment with a long non-<qt> string. Use that to find + // sensible widths for the columns. + // + text->setText(i18n("Send questions and comments to kdepim-users@kde.org")); + text->adjustSize(); + + int linewidth = text->size().width(); + int lineheight = text->size().height(); + + // Use the label to display the applciation icon + text->setText(TQString::null); + text->setPixmap(applicationIcon); + text->adjustSize(); + grid->addWidget(text, 0, 1); + + + KActiveLabel *linktext = new KActiveLabel(w); + grid->addRowSpacing(1,kMax(100,6*lineheight)); + grid->addRowSpacing(2,kMax(100,6*lineheight)); + grid->addColSpacing(2,SPACING+linewidth/2); + grid->addColSpacing(3,SPACING+linewidth/2); + grid->setRowStretch(1,50); + grid->setRowStretch(2,50); + grid->setColStretch(2,50); + grid->setColStretch(3,50); + linktext->setMinimumSize(linewidth,kMax(260,60+12*lineheight)); + linktext->setFixedHeight(kMax(260,60+12*lineheight)); + linktext->setVScrollBarMode(TQScrollView::Auto/*AlwaysOn*/); + text = new TQLabel(w); + grid->addMultiCellWidget(text,0,0,2,3); + grid->addMultiCellWidget(linktext,1,2,1,3); + + // Now set the program and copyright information. + s = CSL1("<qt><h3>"); + s += p->programName(); + s += ' '; + s += p->version(); + s += CSL1("</h3>"); + s += p->copyrightStatement() + CSL1("<br></qt>"); + text->setText(s); + + linktext->append(p->shortDescription() + CSL1("<br>")); + + if (!p->homepage().isEmpty()) + { + s = TQString::null; + s += CSL1("<a href=\"%1\">").arg(p->homepage()); + s += p->homepage(); + s += CSL1("</a><br>"); + linktext->append(s); + } + + s = TQString::null; + s += i18n("Send questions and comments to <a href=\"mailto:%1\">%2</a>.") + .arg( CSL1("kdepim-users@kde.org") ) + .arg( CSL1("kdepim-users@kde.org") ); + s += ' '; + s += i18n("Send bug reports to <a href=\"mailto:%1\">%2</a>.") + .arg(p->bugAddress()) + .arg(p->bugAddress()); + s += ' '; + s += i18n("For trademark information, see the " + "<a href=\"help:/kpilot/trademarks.html\">KPilot User's Guide</a>."); + s += CSL1("<br>"); + linktext->append(s); + linktext->append(TQString::null); + + + + TQValueList<KAboutPerson> pl = p->authors(); + TQValueList<KAboutPerson>::ConstIterator i; + + s = i18n("<b>Authors:</b> "); + + TQString comma = CSL1(", "); + + unsigned int count=1; + for (i=pl.begin(); i!=pl.end(); ++i) + { + s.append(CSL1("%1 (<i>%2</i>)%3") + .arg((*i).name()) + .arg((*i).task()) + .arg(count<pl.count() ? comma : TQString::null) + ); + count++; + } + linktext->append(s); + + s = TQString::null; + pl = p->credits(); + if (pl.count()>0) + { + count=1; + s.append(i18n("<b>Credits:</b> ")); + for (i=pl.begin(); i!=pl.end(); ++i) + { + s.append(CSL1("%1 (<i>%2</i>)%3") + .arg((*i).name()) + .arg((*i).task()) + .arg(count<pl.count() ? comma : TQString::null) + ); + count++; + } + } + linktext->append(s); + linktext->ensureVisible(0,0); + + w->adjustSize(); + + return w; +} + +/* static */ void ConduitConfigBase::addAboutPage(TQTabWidget *tw, + KAboutData *ad) +{ + FUNCTIONSETUP; + + Q_ASSERT(tw); + + TQWidget *w = aboutPage(tw,ad); + TQSize sz = w->size(); + + if (sz.width() < tw->size().width()) + { + sz.setWidth(tw->size().width()); + } + if (sz.height() < tw->size().height()) + { + sz.setHeight(tw->size().height()); + } + + tw->resize(sz); + tw->addTab(w, i18n("About")); + tw->adjustSize(); +} + + + +ConduitAction::ConduitAction(KPilotLink *p, + const char *name, + const TQStringList &args) : + SyncAction(p,name), + fDatabase(0L), + fLocalDatabase(0L), + fCtrHH(0L), + fCtrPC(0L), + fSyncDirection(args), + fConflictResolution(SyncAction::eAskUser), + fFirstSync(false) +{ + FUNCTIONSETUP; + + TQString cResolution(args.grep(TQRegExp(CSL1("--conflictResolution \\d*"))).first()); + if (cResolution.isEmpty()) + { + fConflictResolution=(SyncAction::ConflictResolution) + cResolution.tqreplace(TQRegExp(CSL1("--conflictResolution (\\d*)")), CSL1("\\1")).toInt(); + } + + for (TQStringList::ConstIterator it = args.begin(); + it != args.end(); + ++it) + { + DEBUGKPILOT << fname << ": " << *it << endl; + } + + DEBUGKPILOT << fname << ": Direction=" << fSyncDirection.name() << endl; + fCtrHH = new CUDCounter(i18n("Handheld")); + fCtrPC = new CUDCounter(i18n("PC")); +} + +/* virtual */ ConduitAction::~ConduitAction() +{ + FUNCTIONSETUP; + + KPILOT_DELETE(fDatabase); + KPILOT_DELETE(fLocalDatabase); + + KPILOT_DELETE(fCtrHH); + KPILOT_DELETE(fCtrPC); +} + +bool ConduitAction::openDatabases(const TQString &name, bool *retrieved) +{ + FUNCTIONSETUP; + + DEBUGKPILOT << fname + << ": Trying to open database " + << name << endl; + DEBUGKPILOT << fname + << ": Mode=" + << (syncMode().isTest() ? "test " : "") + << (syncMode().isLocal() ? "local " : "") + << endl ; + + KPILOT_DELETE(fLocalDatabase); + + TQString localPathName = PilotLocalDatabase::getDBPath() + name; + + // we always want to use the conduits/ directory for our local + // databases. this keeps our backups and data that our conduits use + // for record keeping separate + localPathName.tqreplace(CSL1("DBBackup/"), CSL1("conduits/")); + + DEBUGKPILOT << fname << ": localPathName: [" << localPathName + << "]" << endl; + + PilotLocalDatabase *localDB = new PilotLocalDatabase( localPathName ); + + if (!localDB) + { + WARNINGKPILOT << "Could not initialize object for local copy of database \"" + << name + << "\"" << endl; + if (retrieved) *retrieved = false; + return false; + } + + // if there is no backup db yet, fetch it from the palm, open it and set the full sync flag. + if (!localDB->isOpen() ) + { + TQString dbpath(localDB->dbPathName()); + KPILOT_DELETE(localDB); + DEBUGKPILOT << fname + << ": Backup database " << dbpath + << " not found." << endl; + struct DBInfo dbinfo; + +// TODO Extend findDatabase() with extra overload? + if (deviceLink()->findDatabase(Pilot::toPilot( name ), &dbinfo)<0 ) + { + WARNINGKPILOT << "Could not get DBInfo for " << name << endl; + if (retrieved) *retrieved = false; + return false; + } + + DEBUGKPILOT << fname + << ": Found Palm database: " << dbinfo.name <<endl + << fname << ": type = " << dbinfo.type + << " creator = " << dbinfo.creator + << " version = " << dbinfo.version + << " index = " << dbinfo.index << endl; + dbinfo.flags &= ~dlpDBFlagOpen; + + // make sure the dir for the backup db really exists! + TQFileInfo fi(dbpath); + TQString path(TQFileInfo(dbpath).dir(true).absPath()); + if (!path.endsWith(CSL1("/"))) path.append(CSL1("/")); + if (!KStandardDirs::exists(path)) + { + DEBUGKPILOT << fname << ": Trying to create path for database: <" + << path << ">" << endl; + KStandardDirs::makeDir(path); + } + if (!KStandardDirs::exists(path)) + { + DEBUGKPILOT << fname << ": Database directory does not exist." << endl; + if (retrieved) *retrieved = false; + return false; + } + + if (!deviceLink()->retrieveDatabase(dbpath, &dbinfo) ) + { + WARNINGKPILOT << "Could not retrieve database " + << name << " from the handheld." << endl; + if (retrieved) *retrieved = false; + return false; + } + localDB = new PilotLocalDatabase( localPathName ); + if (!localDB || !localDB->isOpen()) + { + WARNINGKPILOT << "local backup of database " << name << " could not be initialized." << endl; + if (retrieved) *retrieved = false; + return false; + } + if (retrieved) *retrieved=true; + } + fLocalDatabase = localDB; + + fDatabase = deviceLink()->database( name ); + + if (!fDatabase) + { + WARNINGKPILOT << "Could not open database \"" + << name + << "\" on the pilot." + << endl; + } + else + { + fCtrHH->setStartCount(fDatabase->recordCount()); + } + + return (fDatabase && fDatabase->isOpen() && + fLocalDatabase && fLocalDatabase->isOpen() ); +} + + +bool ConduitAction::changeSync(SyncMode::Mode m) +{ + FUNCTIONSETUP; + + if ( fSyncDirection.isSync() && SyncMode::eFullSync == m) + { + fSyncDirection.setMode(m); + return true; + } + return false; +} + +void ConduitAction::finished() +{ + FUNCTIONSETUP; + + if (fDatabase && fCtrHH) + fCtrHH->setEndCount(fDatabase->recordCount()); + + if (fCtrHH && fCtrPC) + { + addSyncLogEntry(fCtrHH->moo() +"\n",false); + DEBUGKPILOT << fname << ": " << fCtrHH->moo() << endl; + addSyncLogEntry(fCtrPC->moo() +"\n",false); + DEBUGKPILOT << fname << ": " << fCtrPC->moo() << endl; + + // STEP2 of making sure we don't delete our little user's + // precious data... + // sanity checks for handheld... + int hhVolatility = fCtrHH->percentDeleted() + + fCtrHH->percentUpdated() + + fCtrHH->percentCreated(); + + int pcVolatility = fCtrPC->percentDeleted() + + fCtrPC->percentUpdated() + + fCtrPC->percentCreated(); + + // TODO: allow user to configure this... + // this is a percentage... + int allowedVolatility = 70; + + TQString caption = i18n("Large Changes Detected"); + // args are already i18n'd + TQString query = i18n("The %1 conduit has made a " + "large number of changes to your %2. Do you want " + "to allow this change?\nDetails:\n\t%3"); + + if (hhVolatility > allowedVolatility) + { + query = query.arg(fConduitName) + .arg(fCtrHH->type()).arg(fCtrHH->moo()); + + DEBUGKPILOT << fname << ": Yikes, lots of volatility " + << "caught. Check with user: [" << query + << "]." << endl; + + /* + int rc = questionYesNo(query, caption, + TQString::null, 0 ); + if (rc == KMessageBox::Yes) + { + // TODO: add commit and rollback code. + // note: this will require some thinking, + // since we have to undo changes to the + // pilot databases, changes to the PC + // resources, changes to the mappings files + // (record id mapping, etc.) + } + */ + } + + + } + +} + + +ConduitProxy::ConduitProxy(KPilotLink *p, + const TQString &name, + const SyncAction::SyncMode &m) : + ConduitAction(p,name.latin1(),m.list()), + fDesktopName(name) +{ + FUNCTIONSETUP; +} + +/* virtual */ bool ConduitProxy::exec() +{ + FUNCTIONSETUP; + + // query that service + KSharedPtr < KService > o = KService::serviceByDesktopName(fDesktopName); + if (!o) + { + WARNINGKPILOT << "Can't find desktop file for conduit " + << fDesktopName + << endl; + addSyncLogEntry(i18n("Could not find conduit %1.").arg(fDesktopName)); + return false; + } + + + // load the lib + fLibraryName = o->library(); + DEBUGKPILOT << fname + << ": Loading desktop " + << fDesktopName + << " with lib " + << fLibraryName + << endl; + + KLibrary *library = KLibLoader::self()->library( + TQFile::encodeName(fLibraryName)); + if (!library) + { + WARNINGKPILOT << "Can't load library " + << fLibraryName + << " - " + << KLibLoader::self()->lastErrorMessage() + << endl; + addSyncLogEntry(i18n("Could not load conduit %1.").arg(fDesktopName)); + return false; + } + + unsigned long version = PluginUtility::pluginVersion(library); + if ( Pilot::PLUGIN_API != version ) + { + WARNINGKPILOT << "Library " + << fLibraryName + << " has version " + << version + << endl; + addSyncLogEntry(i18n("Conduit %1 has wrong version (%2).").arg(fDesktopName).arg(version)); + return false; + } + + KLibFactory *factory = library->factory(); + if (!factory) + { + WARNINGKPILOT << "Can't find factory in library " + << fLibraryName + << endl; + addSyncLogEntry(i18n("Could not initialize conduit %1.").arg(fDesktopName)); + return false; + } + + TQStringList l = syncMode().list(); + + DEBUGKPILOT << fname << ": Flags: " << syncMode().name() << endl; + + TQObject *object = factory->create(fHandle,name(),"SyncAction",l); + + if (!object) + { + WARNINGKPILOT << "Can't create SyncAction." << endl; + addSyncLogEntry(i18n("Could not create conduit %1.").arg(fDesktopName)); + return false; + } + + fConduit = dynamic_cast<ConduitAction *>(object); + + if (!fConduit) + { + WARNINGKPILOT << "Can't cast to ConduitAction." << endl; + addSyncLogEntry(i18n("Could not create conduit %1.").arg(fDesktopName)); + return false; + } + + addSyncLogEntry(i18n("[Conduit %1]").arg(fDesktopName)); + + // Handle the syncDone signal properly & unload the conduit. + TQObject::connect(fConduit,TQT_SIGNAL(syncDone(SyncAction *)), + this,TQT_SLOT(execDone(SyncAction *))); + // Proxy all the log and error messages. + TQObject::connect(fConduit,TQT_SIGNAL(logMessage(const TQString &)), + this,TQT_SIGNAL(logMessage(const TQString &))); + TQObject::connect(fConduit,TQT_SIGNAL(logError(const TQString &)), + this,TQT_SIGNAL(logError(const TQString &))); + TQObject::connect(fConduit,TQT_SIGNAL(logProgress(const TQString &,int)), + this,TQT_SIGNAL(logProgress(const TQString &,int))); + + TQTimer::singleShot(0,fConduit,TQT_SLOT(execConduit())); + return true; +} + +void ConduitProxy::execDone(SyncAction *p) +{ + FUNCTIONSETUP; + + if (p!=fConduit) + { + WARNINGKPILOT << "Unknown conduit @" + << (void *) p + << " finished." + << endl; + emit syncDone(this); + return; + } + + // give our worker a chance to sanity check the results... + fConduit->finished(); + + addSyncLogEntry(CSL1("\n"),false); // Put bits of the conduit logs on separate lines + + KPILOT_DELETE(p); + + emit syncDone(this); +} + + +namespace PluginUtility +{ + +TQString findArgument(const TQStringList &a, const TQString &arg) +{ + FUNCTIONSETUP; + + TQString search; + + if (arg.startsWith( CSL1("--") )) + { + search = arg; + } + else + { + search = CSL1("--") + arg; + } + search.append( CSL1("=") ); + + + TQStringList::ConstIterator end = a.end(); + for (TQStringList::ConstIterator i = a.begin(); i != end; ++i) + { + if ((*i).startsWith( search )) + { + TQString s = (*i).mid(search.length()); + return s; + } + } + + return TQString::null; +} + +/* static */ bool isRunning(const TQCString &n) +{ + DCOPClient *dcop = KApplication::kApplication()->dcopClient(); + QCStringList apps = dcop->registeredApplications(); + return apps.tqcontains(n); +} + + +/* static */ unsigned long pluginVersion(const KLibrary *lib) +{ + TQString symbol = CSL1("version_"); + symbol.append(lib->name()); + + if (!lib->hasSymbol(symbol.latin1())) return 0; + + unsigned long *p = (unsigned long *)(lib->symbol(symbol.latin1())); + return *p; +} + + +/* static */ TQString pluginVersionString(const KLibrary *lib) +{ + TQString symbol= CSL1("id_"); + symbol.append(lib->name()); + + if (!lib->hasSymbol(symbol.latin1())) return TQString::null; + + return TQString::tqfromLatin1(*((const char **)(lib->symbol(symbol.latin1())))); +} + + +} + + +CUDCounter::CUDCounter(TQString s) : + fC(0),fU(0),fD(0),fStart(0),fEnd(0),fType(s) +{ +} + +void CUDCounter::created(unsigned int c) +{ + fC += c; +} + +void CUDCounter::updated(unsigned int c) +{ + fU += c; +} + +void CUDCounter::deleted(unsigned int c) +{ + fD += c; +} + +void CUDCounter::setStartCount(unsigned int t) +{ + fStart = t; +} + +void CUDCounter::setEndCount(unsigned int t) +{ + fEnd = t; +} + +TQString CUDCounter::moo() const +{ + TQString result = fType + ": " + + i18n("Start: %1. End: %2. ").arg(fStart).arg(fEnd); + + if (fC > 0) result += i18n("%1 new. ").arg(fC); + if (fU > 0) result += i18n("%1 changed. ").arg(fU); + if (fD > 0) result += i18n("%1 deleted. ").arg(fD); + + if ( (fC+fU+fD) <= 0) result += i18n("No changes made. "); + + return result; +} + + |