/* KPilot ** ** Copyright (C) 1998-2001 by Dan Pilone ** Copyright (C) 2003-2004 Reinhold Kainhofer ** Copyright (C) 2006-2007 Adriaan de Groot ** Copyright (C) 2007 Jason 'vanRijn' Kasper ** */ /* ** 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 "pilotUser.h" #include "pilotSysInfo.h" #include "pilotCard.h" #include "pilotSerialDatabase.h" #include "pilotLocalDatabase.h" #include "kpilotlink.h" #include "kpilotdevicelinkPrivate.moc" #include "kpilotdevicelink.moc" DeviceMap *DeviceMap::mThis = 0L; static inline void startOpenTimer(DeviceCommThread *dev, TQTimer *&t) { if ( !t) { t = new TQTimer(dev); TQObject::connect(t, TQT_SIGNAL(timeout()), dev, TQT_SLOT(openDevice())); } // just a single-shot timer. we'll know when to start it again... t->start(1000, true); } DeviceCommThread::DeviceCommThread(KPilotDeviceLink *d) : TQThread(), fDone(true), fHandle(d), fOpenTimer(0L), fSocketNotifier(0L), fSocketNotifierActive(false), fWorkaroundUSBTimer(0L), fPilotSocket(-1), fTempSocket(-1), fAcceptedCount(0) { FUNCTIONSETUP; } DeviceCommThread::~DeviceCommThread() { FUNCTIONSETUPL(2); close(); KPILOT_DELETE(fWorkaroundUSBTimer); } void DeviceCommThread::close() { FUNCTIONSETUPL(2); KPILOT_DELETE(fWorkaroundUSBTimer); KPILOT_DELETE(fOpenTimer); KPILOT_DELETE(fSocketNotifier); fSocketNotifierActive=false; if (fTempSocket != -1) { DEBUGKPILOT << fname << ": device comm thread closing socket: [" << fTempSocket << "]" << endl; pi_close(fTempSocket); } if (fPilotSocket != -1) { DEBUGKPILOT << fname << ": device comm thread closing socket: [" << fPilotSocket << "]" << endl; pi_close(fPilotSocket); } fTempSocket = (-1); fPilotSocket = (-1); DeviceMap::self()->unbindDevice(link()->fRealPilotPath); } void DeviceCommThread::reset() { FUNCTIONSETUP; if (link()->fMessages->shouldPrint(Messages::OpenFailMessage)) { TQApplication::postEvent(link(), new DeviceCommEvent(EventLogMessage, i18n("Could not open device: %1 (will retry)") .arg(link()->pilotPath() ))); } link()->fMessages->reset(); close(); // Timer already deleted by close() call. startOpenTimer(this,fOpenTimer); link()->fLinkStatus = WaitingForDevice; } /** * This is an asyncronous process. We try to create a socket with the Palm * and then bind to it (in open()). If we're able to do those 2 things, then * we do 2 things: we set a timeout timer (which will tell us that X amount of * time has transpired before we get into the meat of the sync transaction), and * we also set up a TQSocketNotifier, which will tell us when data is available * to be read from the Palm socket. If we were unable to create a socket * and/or bind to the Palm in this method, we'll start our timer again. */ void DeviceCommThread::openDevice() { FUNCTIONSETUPL(2); bool deviceOpened = false; // This transition (from Waiting to Found) can only be // taken once. // if (link()->fLinkStatus == WaitingForDevice) { link()->fLinkStatus = FoundDevice; } if (link()->fMessages->shouldPrint(Messages::OpenMessage)) { TQApplication::postEvent(link(), new DeviceCommEvent(EventLogMessage, i18n("Trying to open device %1...") .arg(link()->fPilotPath))); } // if we're not supposed to be done, try to open the main pilot // path... if (!fDone && link()->fPilotPath.length() > 0) { DEBUGKPILOT << fname << ": Opening main pilot path: [" << link()->fPilotPath << "]." << endl; deviceOpened = open(link()->fPilotPath); } // only try the temp device if our earlier attempt didn't work and the temp // device is different than the main device, and it's a non-empty // string bool tryTemp = !deviceOpened && (link()->fTempDevice.length() > 0) && (link()->fPilotPath != link()->fTempDevice); // if we're not supposed to be done, and we should try the temp // device, try the temp device... if (!fDone && tryTemp) { DEBUGKPILOT << fname << ": Couldn't open main pilot path. " << "Now trying temp device: [" << link()->fTempDevice << "]." << endl; deviceOpened = open(link()->fTempDevice); } // if we couldn't connect, try to connect again... if (!fDone && !deviceOpened) { startOpenTimer(this, fOpenTimer); } } bool DeviceCommThread::open(const TQString &device) { FUNCTIONSETUPL(2); int ret; int e = 0; TQString msg; if (fTempSocket != -1) { pi_close(fTempSocket); } fTempSocket = (-1); link()->fRealPilotPath = KStandardDirs::realFilePath(device.isEmpty() ? link()->fPilotPath : device); if ( !DeviceMap::self()->canBind(link()->fRealPilotPath) ) { msg = i18n("Already listening on that device"); WARNINGKPILOT << "Pilot Path: [" << link()->fRealPilotPath << "] already connected." << endl; WARNINGKPILOT << msg << endl; link()->fLinkStatus = PilotLinkError; TQApplication::postEvent(link(), new DeviceCommEvent(EventLogError, msg)); return false; } DEBUGKPILOT << fname << ": Trying to create socket." << endl; fTempSocket = pi_socket(PI_AF_PILOT, PI_SOCK_STREAM, PI_PF_DLP); if (fTempSocket < 0) { e = errno; msg = i18n("Cannot create socket for communicating " "with the Pilot (%1)").arg(errorMessage(e)); DEBUGKPILOT << msg << endl; DEBUGKPILOT << "(" << strerror(e) << ")" << endl; link()->fLinkStatus = PilotLinkError; TQApplication::postEvent(link(), new DeviceCommEvent(EventLogError, msg)); return false; } DEBUGKPILOT << fname << ": Got socket: [" << fTempSocket << "]" << endl; link()->fLinkStatus = CreatedSocket; DEBUGKPILOT << fname << ": Binding to path: [" << link()->fRealPilotPath << "]" << endl; ret = pi_bind(fTempSocket, TQFile::encodeName(link()->fRealPilotPath)); if (ret < 0) { DEBUGKPILOT << fname << ": pi_bind error: [" << strerror(errno) << "]" << endl; e = errno; msg = i18n("Cannot open Pilot port \"%1\". ").arg(link()->fRealPilotPath); DEBUGKPILOT << msg << endl; DEBUGKPILOT << "(" << strerror(e) << ")" << endl; link()->fLinkStatus = PilotLinkError; if (link()->fMessages->shouldPrint(Messages::OpenFailMessage)) { TQApplication::postEvent(link(), new DeviceCommEvent(EventLogError, msg)); } return false; } link()->fLinkStatus = DeviceOpen; DeviceMap::self()->bindDevice(link()->fRealPilotPath); fSocketNotifier = new TQSocketNotifier(fTempSocket, TQSocketNotifier::Read, this); TQObject::connect(fSocketNotifier, TQT_SIGNAL(activated(int)), this, TQT_SLOT(acceptDevice())); fSocketNotifierActive=true; /** * We _always_ want to set a maximum amount of time that we will wait * for the sync process to start. In the case where our user * has told us that he has a funky USB device, set the workaround timeout * for shorter than normal. */ int timeout=20000; if (link()->fWorkaroundUSB) { timeout=5000; } fWorkaroundUSBTimer = new TQTimer(this); connect(fWorkaroundUSBTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(workaroundUSB())); fWorkaroundUSBTimer->start(timeout, true); return true; } /** * We've been notified by our TQSocketNotifier that we have data available on the * socket. Try to go through the remaining steps of the connnection process. * Note: If we return at all from this before the very end without a successful * connection, we need to make sure we restart our connection open timer, otherwise * it won't be restarted. */ void DeviceCommThread::acceptDevice() { FUNCTIONSETUP; int ret; /** * Our socket notifier should be the only reason that we end up here. * If we're here without him being active, we have a problem. Try to clean * up and get out. */ if (!fSocketNotifierActive) { if (!fAcceptedCount) { kdWarning() << k_funcinfo << ": Accidentally in acceptDevice()" << endl; } fAcceptedCount++; if (fAcceptedCount>10) { // Damn the torpedoes KPILOT_DELETE(fSocketNotifier); } return; } if (fSocketNotifier) { // fSocketNotifier->setEnabled(false); fSocketNotifierActive=false; KPILOT_DELETE(fSocketNotifier); } DEBUGKPILOT << fname << ": Found connection on device: [" << link()->pilotPath().latin1() << "]." <statusString() << "] and socket: [" << fTempSocket << "]" << endl; ret = pi_listen(fTempSocket, 1); if (ret < 0) { char *s = strerror(errno); WARNINGKPILOT << "pi_listen returned: [" << s << "]" << endl; // Presumably, strerror() returns things in // local8Bit and not latin1. TQApplication::postEvent(link(), new DeviceCommEvent(EventLogError, i18n("Cannot listen on Pilot socket (%1)"). arg(TQString::fromLocal8Bit(s)))); reset(); return; } TQApplication::postEvent(link(), new DeviceCommEvent(EventLogProgress, TQString(), 10)); DEBUGKPILOT << fname << ": Listening to pilot. Now trying accept..." << endl; int timeout = 20; fPilotSocket = pi_accept_to(fTempSocket, 0, 0, timeout); if (fPilotSocket < 0) { char *s = strerror(errno); WARNINGKPILOT << "pi_accept returned: [" << s << "]" << endl; TQApplication::postEvent(link(), new DeviceCommEvent(EventLogError, i18n("Cannot accept Pilot (%1)") .arg(TQString::fromLocal8Bit(s)))); link()->fLinkStatus = PilotLinkError; reset(); return; } DEBUGKPILOT << fname << ": Link accept done." << endl; if ((link()->fLinkStatus != DeviceOpen) || (fPilotSocket == -1)) { link()->fLinkStatus = PilotLinkError; WARNINGKPILOT << "Already connected or unable to connect!" << endl; TQApplication::postEvent(link(), new DeviceCommEvent(EventLogError, i18n("Cannot accept Pilot (%1)") .arg(i18n("already connected")))); reset(); return; } TQApplication::postEvent(link(), new DeviceCommEvent(EventLogProgress, TQString(), 30)); DEBUGKPILOT << fname << ": doing dlp_ReadSysInfo..." << endl; struct SysInfo sys_info; if (dlp_ReadSysInfo(fPilotSocket, &sys_info) < 0) { TQApplication::postEvent(link(), new DeviceCommEvent(EventLogError, i18n("Unable to read system information from Pilot"))); link()->fLinkStatus=PilotLinkError; reset(); return; } else { DEBUGKPILOT << fname << ": dlp_ReadSysInfo successful..." << endl; KPILOT_DELETE(link()->fPilotSysInfo); link()->fPilotSysInfo = new KPilotSysInfo(&sys_info); DEBUGKPILOT << fname << ": RomVersion: [" << link()->fPilotSysInfo->getRomVersion() << "] Locale: [" << link()->fPilotSysInfo->getLocale() << "] Product: [" << link()->fPilotSysInfo->getProductID() << "]" << endl; } // If we've made it this far, make sure our USB workaround timer doesn't fire! fWorkaroundUSBTimer->stop(); KPILOT_DELETE(fWorkaroundUSBTimer); TQApplication::postEvent(link(), new DeviceCommEvent(EventLogProgress, TQString(), 60)); KPILOT_DELETE(link()->fPilotUser); link()->fPilotUser = new KPilotUser; DEBUGKPILOT << fname << ": doing dlp_ReadUserInfo..." << endl; /* Ask the pilot who it is. And see if it's who we think it is. */ dlp_ReadUserInfo(fPilotSocket, link()->fPilotUser->data()); TQString n = link()->getPilotUser().name(); DEBUGKPILOT << fname << ": Read user name: [" << n << "]" << endl; TQApplication::postEvent(link(), new DeviceCommEvent(EventLogProgress, i18n("Checking last PC..."), 90)); /* Tell user (via Pilot) that we are starting things up */ if ((ret=dlp_OpenConduit(fPilotSocket)) < 0) { DEBUGKPILOT << fname << ": dlp_OpenConduit returned: [" << ret << "]" << endl; TQApplication::postEvent(link(), new DeviceCommEvent(EventLogError, i18n("Could not read user information from the Pilot. " "Perhaps you have a password set on the device?"))); } link()->fLinkStatus = AcceptedDevice; TQApplication::postEvent(link(), new DeviceCommEvent(EventLogProgress, TQString(), 100)); DeviceCommEvent * ev = new DeviceCommEvent(EventDeviceReady); ev->setCurrentSocket(fPilotSocket); TQApplication::postEvent(link(), ev); } void DeviceCommThread::workaroundUSB() { FUNCTIONSETUP; reset(); } void DeviceCommThread::run() { FUNCTIONSETUP; fDone = false; startOpenTimer(this, fOpenTimer); int sleepBetweenPoll = 2; // keep the thread alive until we're supposed to be done while (!fDone) { TQThread::sleep(sleepBetweenPoll); } close(); // now sleep one last bit to make sure the pthread inside // pilot-link (potentially, if it's libusb) is done before we exit TQThread::sleep(1); DEBUGKPILOT << fname << ": comm thread now done..." << endl; } KPilotDeviceLink::KPilotDeviceLink(TQObject * parent, const char *name, const TQString &tempDevice) : KPilotLink(parent, name), fLinkStatus(Init), fWorkaroundUSB(false), fPilotSocket(-1), fTempDevice(tempDevice), fMessages(new Messages(this)), fDeviceCommThread(0L) { FUNCTIONSETUP; DEBUGKPILOT << fname << ": Pilot-link version: [" << PILOT_LINK_NUMBER << "]" << endl; } KPilotDeviceLink::~KPilotDeviceLink() { FUNCTIONSETUP; close(); KPILOT_DELETE(fPilotSysInfo); KPILOT_DELETE(fPilotUser); KPILOT_DELETE(fMessages); } /* virtual */bool KPilotDeviceLink::isConnected() const { return fLinkStatus == AcceptedDevice; } /* virtual */bool KPilotDeviceLink::event(TQEvent *e) { FUNCTIONSETUP; bool handled = false; if ((int)e->type() == EventDeviceReady) { DeviceCommEvent* t = static_cast(e); fPilotSocket = t->currentSocket(); emit deviceReady( this); handled = true; } else if ((int)e->type() == EventLogMessage) { DeviceCommEvent* t = static_cast(e); emit logMessage(t->message()); handled = true; } else if ((int)e->type() == EventLogError) { DeviceCommEvent* t = static_cast(e); emit logError(t->message()); handled = true; } else if ((int)e->type() == EventLogProgress) { DeviceCommEvent* t = static_cast(e); emit logProgress(t->message(), t->progress()); handled = true; } else { handled = KPilotLink::event(e); } return handled; } void KPilotDeviceLink::stopCommThread() { FUNCTIONSETUP; if (fDeviceCommThread) { fDeviceCommThread->setDone(true); // try to wait for our thread to finish, but don't // block the main thread forever if (fDeviceCommThread->running()) { DEBUGKPILOT << fname << ": comm thread still running. " << "waiting for it to complete." << endl; bool done = fDeviceCommThread->wait(5000); if (!done) { DEBUGKPILOT << fname << ": comm thread still running " << "after wait(). " << "going to have to terminate it." << endl; // not normally to be done, but we must make sure // that this device doesn't come back alive fDeviceCommThread->terminate(); fDeviceCommThread->wait(); } } fDeviceCommThread->close(); KPILOT_DELETE(fDeviceCommThread); } } void KPilotDeviceLink::close() { FUNCTIONSETUP; stopCommThread(); fPilotSocket = (-1); } void KPilotDeviceLink::reset(const TQString & dP) { FUNCTIONSETUP; fLinkStatus = Init; // Release all resources // close(); fPilotPath = TQString(); fPilotPath = dP; if (fPilotPath.isEmpty()) fPilotPath = fTempDevice; if (fPilotPath.isEmpty()) return; reset(); } void KPilotDeviceLink::startCommThread() { FUNCTIONSETUP; stopCommThread(); if (fTempDevice.isEmpty() && pilotPath().isEmpty()) { WARNINGKPILOT << "No point in trying empty device." << endl; TQString msg = i18n("The Pilot device is not configured yet."); WARNINGKPILOT << msg << endl; fLinkStatus = PilotLinkError; emit logError(msg); return; } fDeviceCommThread = new DeviceCommThread(this); fDeviceCommThread->start(); } void KPilotDeviceLink::reset() { FUNCTIONSETUP; fMessages->reset(); close(); checkDevice(); fLinkStatus = WaitingForDevice; startCommThread(); } void KPilotDeviceLink::checkDevice() { // If the device exists yet doesn't have the right // permissions, complain and then continue anyway. // TQFileInfo fi(fPilotPath); if (fi.exists()) { // If it exists, it ought to be RW already. // if (!(fi.isReadable() && fi.isWritable())) { emit logError(i18n("Pilot device %1 is not read-write.") .arg(fPilotPath)); } } else { // It doesn't exist, mention this in the log // (relevant as long as we use only one device type) // emit logError(i18n("Pilot device %1 does not exist. " "Probably it is a USB device and will appear during a HotSync.") .arg(fPilotPath)); // Suppress all normal and error messages about opening the device. fMessages->block(Messages::OpenMessage | Messages::OpenFailMessage, true); } } void KPilotDeviceLink::setTempDevice(const TQString &d) { fTempDevice = d; DeviceMap::self()->bindDevice(fTempDevice); } /* virtual */bool KPilotDeviceLink::tickle() { // No FUNCTIONSETUP here because it may be called from // a separate thread. return pi_tickle(pilotSocket()) >= 0; } /* virtual */void KPilotDeviceLink::addSyncLogEntryImpl(const TQString &entry) { dlp_AddSyncLogEntry(fPilotSocket, const_cast((const char *)Pilot::toPilot(entry))); } bool KPilotDeviceLink::installFile(const TQString & f, const bool deleteFile) { FUNCTIONSETUP; DEBUGKPILOT << fname << ": Installing file " << f << endl; if (!TQFile::exists(f)) return false; char buffer[PATH_MAX]; memset(buffer, 0, PATH_MAX); strlcpy(buffer, TQFile::encodeName(f), PATH_MAX); struct pi_file *pf = pi_file_open(buffer); if (!f) { WARNINGKPILOT << "Cannot open file " << f << endl; emit logError(i18n ("Cannot install the file "%1"."). arg(f)); return false; } if (pi_file_install(pf, fPilotSocket, 0, 0L) < 0) { WARNINGKPILOT << "Cannot pi_file_install " << f << endl; emit logError(i18n ("Cannot install the file "%1"."). arg(f)); return false; } pi_file_close(pf); if (deleteFile) TQFile::remove(f); return true; } int KPilotDeviceLink::openConduit() { return dlp_OpenConduit(fPilotSocket); } TQString KPilotDeviceLink::statusString(LinkStatus l) { TQString s= CSL1("KPilotDeviceLink="); switch (l) { case Init: s.append(CSL1("Init")); break; case WaitingForDevice: s.append(CSL1("WaitingForDevice")); break; case FoundDevice: s.append(CSL1("FoundDevice")); break; case CreatedSocket: s.append(CSL1("CreatedSocket")); break; case DeviceOpen: s.append(CSL1("DeviceOpen")); break; case AcceptedDevice: s.append(CSL1("AcceptedDevice")); break; case SyncDone: s.append(CSL1("SyncDone")); break; case PilotLinkError: s.append(CSL1("PilotLinkError")); break; case WorkaroundUSB: s.append(CSL1("WorkaroundUSB")); break; } return s; } TQString KPilotDeviceLink::statusString() const { return statusString(status() ); } void KPilotDeviceLink::endSync(EndOfSyncFlags f) { FUNCTIONSETUP; if (UpdateUserInfo == f) { getPilotUser().setLastSyncPC((unsigned long) gethostid()); getPilotUser().setLastSyncDate(time(0)); DEBUGKPILOT << fname << ": Writing username " << getPilotUser().name() << endl; dlp_WriteUserInfo(pilotSocket(), getPilotUser().data()); addSyncLogEntry(i18n("End of HotSync\n")); } dlp_EndOfSync(pilotSocket(), 0); KPILOT_DELETE(fPilotSysInfo); KPILOT_DELETE(fPilotUser); } int KPilotDeviceLink::getNextDatabase(int index, struct DBInfo *dbinfo) { FUNCTIONSETUP; pi_buffer_t buf = { 0, 0, 0 }; int r = dlp_ReadDBList(pilotSocket(), 0, dlpDBListRAM, index, &buf); if (r >= 0) { memcpy(dbinfo, buf.data, sizeof(struct DBInfo)); } return r; } // Find a database with the given name. Info about the DB is stored into dbinfo (e.g. to be used later on with retrieveDatabase). int KPilotDeviceLink::findDatabase(const char *name, struct DBInfo *dbinfo, int index, unsigned long type, unsigned long creator) { FUNCTIONSETUP; return dlp_FindDBInfo(pilotSocket(), 0, index, const_cast(name), type, creator, dbinfo); } bool KPilotDeviceLink::retrieveDatabase(const TQString &fullBackupName, DBInfo *info) { FUNCTIONSETUP; if (fullBackupName.isEmpty() || !info) { // Don't even bother trying to convert or retrieve. return false; } DEBUGKPILOT << fname << ": Writing DB <" << info->name << "> " << " to " << fullBackupName << endl; TQCString encodedName = TQFile::encodeName(fullBackupName); struct pi_file *f = pi_file_create(encodedName, info); if (!f) { WARNINGKPILOT << "Failed, unable to create file" << endl; return false; } if (pi_file_retrieve(f, pilotSocket(), 0, 0L) < 0) { WARNINGKPILOT << "Failed, unable to back up database" << endl; pi_file_close(f); return false; } pi_file_close(f); return true; } KPilotLink::DBInfoList KPilotDeviceLink::getDBList(int cardno, int flags) { bool cont=true; DBInfoList dbs; int index=0; while (cont) { pi_buffer_t buf = { 0, 0, 0 }; pi_buffer_clear(&buf); // DBInfo*dbi=new DBInfo(); if (dlp_ReadDBList(pilotSocket(), cardno, flags | dlpDBListMultiple, index, &buf)<0) { cont=false; } else { DBInfo db_n; DBInfo *db_it = (DBInfo *)buf.data; int info_count = buf.used / sizeof(struct DBInfo); while (info_count>0) { memcpy(&db_n, db_it, sizeof(struct DBInfo)); ++db_it; info_count--; dbs.append(db_n); } index=db_n.index+1; } } return dbs; } const KPilotCard *KPilotDeviceLink::getCardInfo(int card) { KPilotCard *cardinfo=new KPilotCard(); if (dlp_ReadStorageInfo(pilotSocket(), card, cardinfo->cardInfo())<0) { WARNINGKPILOT << "Could not get info for card " << card << endl; KPILOT_DELETE(cardinfo); return 0L; } return cardinfo; } PilotDatabase *KPilotDeviceLink::database(const TQString &name) { return new PilotSerialDatabase( this, name ); } PilotDatabase *KPilotDeviceLink::database(const DBInfo *info) { return new PilotSerialDatabase( this, info ); }