/* KPilot ** ** Copyright (C) 2001 by Dan Pilone ** Copyright (C) 2003-2004 Reinhold Kainhofer ** ** 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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("The %1 conduit's settings have been changed. Do you " "want to save the changes before continuing?").tqarg(this->conduitName()); } /* virtual */ bool ConduitConfigBase::maybeSave() { FUNCTIONSETUP; if (!isModified()) return true; int r = KMessageBox::questionYesNoCancel(fWidget, maybeSaveText(), i18n("%1 Conduit").tqarg(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- string. Use that to find // sensible widths for the columns. // text->setText(i18n("Send questions and comments to tdepim-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()); 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("

"); s += p->programName(); s += ' '; s += p->version(); s += CSL1("

"); s += p->copyrightStatement() + CSL1("
"); text->setText(s); linktext->append(p->shortDescription() + CSL1("
")); if (!p->homepage().isEmpty()) { s = TQString(); s += CSL1("").tqarg(p->homepage()); s += p->homepage(); s += CSL1("
"); linktext->append(s); } s = TQString(); s += i18n("Send questions and comments to %2.") .tqarg( CSL1("tdepim-users@kde.org") ) .tqarg( CSL1("tdepim-users@kde.org") ); s += ' '; s += i18n("Send bug reports to %2.") .tqarg(p->bugAddress()) .tqarg(p->bugAddress()); s += ' '; s += i18n("For trademark information, see the " "KPilot User's Guide."); s += CSL1("
"); linktext->append(s); linktext->append(TQString()); TQValueList pl = p->authors(); TQValueList::ConstIterator i; s = i18n("Authors: "); TQString comma = CSL1(", "); unsigned int count=1; for (i=pl.begin(); i!=pl.end(); ++i) { s.append(CSL1("%1 (%2)%3") .tqarg((*i).name()) .tqarg((*i).task()) .tqarg(countappend(s); s = TQString(); pl = p->credits(); if (pl.count()>0) { count=1; s.append(i18n("Credits: ")); for (i=pl.begin(); i!=pl.end(); ++i) { s.append(CSL1("%1 (%2)%3") .tqarg((*i).name()) .tqarg((*i).task()) .tqarg(countappend(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.replace(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.replace(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; 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.tqarg(fConduitName) .tqarg(fCtrHH->type()).tqarg(fCtrHH->moo()); DEBUGKPILOT << fname << ": Yikes, lots of volatility " << "caught. Check with user: [" << query << "]." << endl; /* int rc = questionYesNo(query, caption, TQString(), 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.").tqarg(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.").tqarg(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).").tqarg(fDesktopName).tqarg(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.").tqarg(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.").tqarg(fDesktopName)); return false; } fConduit = dynamic_cast(object); if (!fConduit) { WARNINGKPILOT << "Can't cast to ConduitAction." << endl; addSyncLogEntry(i18n("Could not create conduit %1.").tqarg(fDesktopName)); return false; } addSyncLogEntry(i18n("[Conduit %1]").tqarg(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(); } /* static */ bool isRunning(const TQCString &n) { DCOPClient *dcop = KApplication::kApplication()->dcopClient(); QCStringList apps = dcop->registeredApplications(); return apps.contains(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(); 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. ").tqarg(fStart).tqarg(fEnd); if (fC > 0) result += i18n("%1 new. ").tqarg(fC); if (fU > 0) result += i18n("%1 changed. ").tqarg(fU); if (fD > 0) result += i18n("%1 deleted. ").tqarg(fD); if ( (fC+fU+fD) <= 0) result += i18n("No changes made. "); return result; }