You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
763 lines
14 KiB
763 lines
14 KiB
/* KPilot
|
|
**
|
|
** Copyright (C) 1998-2001 by Dan Pilone
|
|
** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
|
|
**
|
|
** This defines an interface to Pilot databases on the local disk.
|
|
*/
|
|
|
|
/*
|
|
** 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 <stdio.h>
|
|
#include <unistd.h>
|
|
#include <assert.h>
|
|
|
|
#include <iostream>
|
|
|
|
#include <pi-file.h>
|
|
|
|
#include <tqstring.h>
|
|
#include <tqfile.h>
|
|
#include <tqregexp.h>
|
|
#include <tqdatetime.h>
|
|
#include <tqvaluevector.h>
|
|
|
|
#include <kdebug.h>
|
|
#include <kglobal.h>
|
|
#include <kstandarddirs.h>
|
|
#include <ksavefile.h>
|
|
|
|
#include "pilotRecord.h"
|
|
#include "pilotLocalDatabase.h"
|
|
|
|
typedef TQValueVector<PilotRecord *> Records;
|
|
|
|
class PilotLocalDatabase::Private : public Records
|
|
{
|
|
public:
|
|
static const int DEFAULT_SIZE = 128;
|
|
Private(int size=DEFAULT_SIZE) : Records(size) { resetIndex(); }
|
|
~Private() { deleteRecords(); }
|
|
|
|
void deleteRecords()
|
|
{
|
|
for (unsigned int i=0; i<size(); i++)
|
|
{
|
|
delete at(i);
|
|
}
|
|
clear();
|
|
resetIndex();
|
|
}
|
|
|
|
void resetIndex()
|
|
{
|
|
current = 0;
|
|
pending = -1;
|
|
}
|
|
|
|
unsigned int current;
|
|
int pending;
|
|
} ;
|
|
|
|
PilotLocalDatabase::PilotLocalDatabase(const TQString & path,
|
|
const TQString & dbName, bool useDefaultPath) :
|
|
PilotDatabase(dbName),
|
|
fPathName(path),
|
|
fDBName(dbName),
|
|
fAppInfo(0L),
|
|
fAppLen(0),
|
|
d(0L)
|
|
{
|
|
FUNCTIONSETUP;
|
|
fixupDBName();
|
|
openDatabase();
|
|
|
|
if (!isOpen() && useDefaultPath)
|
|
{
|
|
if (fPathBase && !fPathBase->isEmpty())
|
|
{
|
|
fPathName = *fPathBase;
|
|
}
|
|
else
|
|
{
|
|
fPathName = KGlobal::dirs()->saveLocation("data",
|
|
CSL1("kpilot/DBBackup/"));
|
|
}
|
|
fixupDBName();
|
|
openDatabase();
|
|
if (!isOpen())
|
|
{
|
|
fPathName=path;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
PilotLocalDatabase::PilotLocalDatabase(const TQString &dbName) :
|
|
PilotDatabase( TQString() ),
|
|
fPathName( TQString() ),
|
|
fDBName( TQString() ),
|
|
fAppInfo(0L),
|
|
fAppLen(0),
|
|
d(0L)
|
|
{
|
|
FUNCTIONSETUP;
|
|
|
|
int p = dbName.findRev( '/' );
|
|
if (p<0)
|
|
{
|
|
// No slash
|
|
fPathName = CSL1(".");
|
|
fDBName = dbName;
|
|
}
|
|
else
|
|
{
|
|
fPathName = dbName.left(p);
|
|
fDBName = dbName.mid(p+1);
|
|
}
|
|
openDatabase();
|
|
}
|
|
|
|
PilotLocalDatabase::~PilotLocalDatabase()
|
|
{
|
|
FUNCTIONSETUP;
|
|
|
|
closeDatabase();
|
|
delete[]fAppInfo;
|
|
delete d;
|
|
}
|
|
|
|
// Changes any forward slashes to underscores
|
|
void PilotLocalDatabase::fixupDBName()
|
|
{
|
|
FUNCTIONSETUP;
|
|
fDBName = fDBName.replace(CSL1("/"),CSL1("_"));
|
|
}
|
|
|
|
bool PilotLocalDatabase::createDatabase(long creator, long type, int, int flags, int version)
|
|
{
|
|
FUNCTIONSETUP;
|
|
|
|
// if the database is already open, we cannot create it again.
|
|
// How about completely resetting it? (i.e. deleting it and then
|
|
// creating it again)
|
|
if (isOpen())
|
|
{
|
|
DEBUGKPILOT << fname << ": Database " << fDBName
|
|
<< " already open. Cannot recreate it." << endl;
|
|
return true;
|
|
}
|
|
|
|
DEBUGKPILOT << fname << ": Creating database " << fDBName << endl;
|
|
|
|
// Database names seem to be latin1.
|
|
Pilot::toPilot(fDBName, fDBInfo.name, sizeof(fDBInfo.name));
|
|
fDBInfo.creator=creator;
|
|
fDBInfo.type=type;
|
|
fDBInfo.more=0;
|
|
fDBInfo.flags=flags;
|
|
fDBInfo.miscFlags=0;
|
|
fDBInfo.version=version;
|
|
fDBInfo.modnum=0;
|
|
fDBInfo.index=0;
|
|
fDBInfo.createDate=(TQDateTime::tqcurrentDateTime()).toTime_t();
|
|
fDBInfo.modifyDate=(TQDateTime::tqcurrentDateTime()).toTime_t();
|
|
fDBInfo.backupDate=(TQDateTime::tqcurrentDateTime()).toTime_t();
|
|
|
|
delete[] fAppInfo;
|
|
fAppInfo=0L;
|
|
fAppLen=0;
|
|
|
|
d = new Private;
|
|
|
|
// TODO: Do I have to open it explicitly???
|
|
setDBOpen(true);
|
|
return true;
|
|
}
|
|
|
|
int PilotLocalDatabase::deleteDatabase()
|
|
{
|
|
FUNCTIONSETUP;
|
|
if (isOpen())
|
|
{
|
|
closeDatabase();
|
|
}
|
|
|
|
TQString dbpath=dbPathName();
|
|
TQFile fl(dbpath);
|
|
if (TQFile::remove(dbPathName()))
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Reads the application block info
|
|
int PilotLocalDatabase::readAppBlock(unsigned char *buffer, int size)
|
|
{
|
|
FUNCTIONSETUP;
|
|
|
|
size_t m = kMin((size_t)size,(size_t)fAppLen);
|
|
|
|
if (!isOpen())
|
|
{
|
|
WARNINGKPILOT << "DB not open!" << endl;
|
|
memset(buffer,0,m);
|
|
return -1;
|
|
}
|
|
|
|
memcpy((void *) buffer, fAppInfo, m);
|
|
return fAppLen;
|
|
}
|
|
|
|
int PilotLocalDatabase::writeAppBlock(unsigned char *buffer, int len)
|
|
{
|
|
FUNCTIONSETUP;
|
|
|
|
if (!isOpen())
|
|
{
|
|
WARNINGKPILOT << "DB not open!" << endl;
|
|
return -1;
|
|
}
|
|
delete[]fAppInfo;
|
|
fAppLen = len;
|
|
fAppInfo = new char[fAppLen];
|
|
|
|
memcpy(fAppInfo, (void *) buffer, fAppLen);
|
|
return 0;
|
|
}
|
|
|
|
|
|
// returns the number of records in the database
|
|
unsigned int PilotLocalDatabase::recordCount() const
|
|
{
|
|
if (d && isOpen())
|
|
{
|
|
return d->size();
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
// Returns a TQValueList of all record ids in the database.
|
|
TQValueList<recordid_t> PilotLocalDatabase::idList()
|
|
{
|
|
int idlen=recordCount();
|
|
TQValueList<recordid_t> idlist;
|
|
if (idlen<=0)
|
|
{
|
|
return idlist;
|
|
}
|
|
|
|
// now create the TQValue list from the idarr:
|
|
for (int i=0; i<idlen; i++)
|
|
{
|
|
idlist.append((*d)[i]->id());
|
|
}
|
|
|
|
return idlist;
|
|
}
|
|
|
|
// Reads a record from database by id, returns record length
|
|
PilotRecord *PilotLocalDatabase::readRecordById(recordid_t id)
|
|
{
|
|
FUNCTIONSETUP;
|
|
|
|
if (!isOpen())
|
|
{
|
|
WARNINGKPILOT << "Database '" << fDBName << " not open!" << endl;
|
|
return 0L;
|
|
}
|
|
|
|
d->pending = -1;
|
|
|
|
for (unsigned int i = 0; i < d->size(); i++)
|
|
{
|
|
if ((*d)[i]->id() == id)
|
|
{
|
|
PilotRecord *newRecord = new PilotRecord((*d)[i]);
|
|
d->current = i;
|
|
return newRecord;
|
|
}
|
|
}
|
|
return 0L;
|
|
}
|
|
|
|
// Reads a record from database, returns the record
|
|
PilotRecord *PilotLocalDatabase::readRecordByIndex(int index)
|
|
{
|
|
FUNCTIONSETUP;
|
|
|
|
if (index < 0)
|
|
{
|
|
DEBUGKPILOT << fname << ": Index " << index << " is bogus." << endl;
|
|
return 0L;
|
|
}
|
|
|
|
d->pending = -1;
|
|
if (!isOpen())
|
|
{
|
|
WARNINGKPILOT << "DB not open!" << endl;
|
|
return 0L;
|
|
}
|
|
|
|
DEBUGKPILOT << fname << ": Index=" << index << " Count=" << recordCount() << endl;
|
|
|
|
if ( (unsigned int)index >= recordCount() )
|
|
{
|
|
return 0L;
|
|
}
|
|
PilotRecord *newRecord = new PilotRecord((*d)[index]);
|
|
d->current = index;
|
|
|
|
return newRecord;
|
|
}
|
|
|
|
// Reads the next record from database in category 'category'
|
|
PilotRecord *PilotLocalDatabase::readNextRecInCategory(int category)
|
|
{
|
|
FUNCTIONSETUP;
|
|
d->pending = -1;
|
|
if (!isOpen())
|
|
{
|
|
WARNINGKPILOT << "DB not open!" << endl;
|
|
return 0L;
|
|
}
|
|
|
|
while ((d->current < d->size())
|
|
&& ((*d)[d->current]->category() != category))
|
|
{
|
|
d->current++;
|
|
}
|
|
|
|
if (d->current >= d->size())
|
|
return 0L;
|
|
PilotRecord *newRecord = new PilotRecord((*d)[d->current]);
|
|
|
|
d->current++; // so we skip it next time
|
|
return newRecord;
|
|
}
|
|
|
|
const PilotRecord *PilotLocalDatabase::findNextNewRecord()
|
|
{
|
|
FUNCTIONSETUP;
|
|
|
|
if (!isOpen())
|
|
{
|
|
WARNINGKPILOT << "DB not open!" << endl;
|
|
return 0L;
|
|
}
|
|
DEBUGKPILOT << fname << ": looking for new record from " << d->current << endl;
|
|
// Should this also check for deleted?
|
|
while ((d->current < d->size())
|
|
&& ((*d)[d->current]->id() != 0 ))
|
|
{
|
|
d->current++;
|
|
}
|
|
|
|
if (d->current >= d->size())
|
|
return 0L;
|
|
|
|
d->pending = d->current; // Record which one needs the new id
|
|
d->current++; // so we skip it next time
|
|
return (*d)[d->pending];
|
|
}
|
|
|
|
PilotRecord *PilotLocalDatabase::readNextModifiedRec(int *ind)
|
|
{
|
|
FUNCTIONSETUP;
|
|
|
|
if (!isOpen())
|
|
{
|
|
WARNINGKPILOT << "DB not open!" << endl;
|
|
return 0L;
|
|
}
|
|
|
|
d->pending = -1;
|
|
// Should this also check for deleted?
|
|
while ((d->current < d->size())
|
|
&& !((*d)[d->current]->isModified()) && ((*d)[d->current]->id()>0 ))
|
|
{
|
|
d->current++;
|
|
}
|
|
|
|
if (d->current >= d->size())
|
|
{
|
|
return 0L;
|
|
}
|
|
PilotRecord *newRecord = new PilotRecord((*d)[d->current]);
|
|
if (ind)
|
|
{
|
|
*ind=d->current;
|
|
}
|
|
|
|
d->pending = d->current; // Record which one needs the new id
|
|
d->current++; // so we skip it next time
|
|
return newRecord;
|
|
}
|
|
|
|
// Writes a new ID to the record specified the index. Not supported on Serial connections
|
|
recordid_t PilotLocalDatabase::updateID(recordid_t id)
|
|
{
|
|
FUNCTIONSETUP;
|
|
|
|
if (!isOpen())
|
|
{
|
|
WARNINGKPILOT << "DB not open!" << endl;
|
|
return 0;
|
|
}
|
|
if (d->pending < 0)
|
|
{
|
|
WARNINGKPILOT << "Last call was NOT readNextModifiedRec()" << endl;
|
|
return 0;
|
|
}
|
|
(*d)[d->pending]->setID(id);
|
|
d->pending = -1;
|
|
return id;
|
|
}
|
|
|
|
// Writes a new record to database (if 'id' == 0, it is assumed that this is a new record to be installed on pilot)
|
|
recordid_t PilotLocalDatabase::writeRecord(PilotRecord * newRecord)
|
|
{
|
|
FUNCTIONSETUP;
|
|
|
|
if (!isOpen())
|
|
{
|
|
WARNINGKPILOT << "DB not open!" << endl;
|
|
return 0;
|
|
}
|
|
|
|
d->pending = -1;
|
|
if (!newRecord)
|
|
{
|
|
WARNINGKPILOT << "Record to be written is invalid!" << endl;
|
|
return 0;
|
|
}
|
|
|
|
// Instead of making the app do it, assume that whenever a record is
|
|
// written to the database it is dirty. (You can clean up the database with
|
|
// resetSyncFlags().) This will make things get copied twice during a hot-sync
|
|
// but shouldn't cause any other major headaches.
|
|
newRecord->setModified( true );
|
|
|
|
// First check to see if we have this record:
|
|
if (newRecord->id() != 0)
|
|
{
|
|
for (unsigned int i = 0; i < d->size(); i++)
|
|
if ((*d)[i]->id() == newRecord->id())
|
|
{
|
|
delete (*d)[i];
|
|
|
|
(*d)[i] = new PilotRecord(newRecord);
|
|
return 0;
|
|
}
|
|
}
|
|
// Ok, we don't have it, so just tack it on.
|
|
d->append( new PilotRecord(newRecord) );
|
|
return newRecord->id();
|
|
}
|
|
|
|
// Deletes a record with the given recordid_t from the database, or all records, if all is set to true. The recordid_t will be ignored in this case
|
|
int PilotLocalDatabase::deleteRecord(recordid_t id, bool all)
|
|
{
|
|
FUNCTIONSETUP;
|
|
if (!isOpen())
|
|
{
|
|
WARNINGKPILOT <<"DB not open"<<endl;
|
|
return -1;
|
|
}
|
|
d->resetIndex();
|
|
if (all)
|
|
{
|
|
d->deleteRecords();
|
|
d->clear();
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
Private::Iterator i;
|
|
for ( i=d->begin() ; i!=d->end(); ++i)
|
|
{
|
|
if ((*i) && (*i)->id() == id) break;
|
|
}
|
|
if ( (i!=d->end()) && (*i) && (*i)->id() == id)
|
|
{
|
|
d->erase(i);
|
|
}
|
|
else
|
|
{
|
|
// Record with this id does not exist!
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
// Resets all records in the database to not dirty.
|
|
int PilotLocalDatabase::resetSyncFlags()
|
|
{
|
|
FUNCTIONSETUP;
|
|
|
|
if (!isOpen())
|
|
{
|
|
WARNINGKPILOT << "DB not open!" << endl;
|
|
return -1;
|
|
}
|
|
d->pending = -1;
|
|
for (unsigned int i = 0; i < d->size(); i++)
|
|
{
|
|
(*d)[i]->setModified( false );
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Resets next record index to beginning
|
|
int PilotLocalDatabase::resetDBIndex()
|
|
{
|
|
FUNCTIONSETUP;
|
|
if (!isOpen())
|
|
{
|
|
WARNINGKPILOT << "DB not open!" << endl;
|
|
return -1;
|
|
}
|
|
d->resetIndex();
|
|
return 0;
|
|
}
|
|
|
|
// Purges all Archived/Deleted records from Palm Pilot database
|
|
int PilotLocalDatabase::cleanup()
|
|
{
|
|
FUNCTIONSETUP;
|
|
if (!isOpen())
|
|
{
|
|
WARNINGKPILOT << "DB not open!" << endl;
|
|
return -1;
|
|
}
|
|
d->resetIndex();
|
|
|
|
/* Not the for loop one might expect since when we erase()
|
|
* a record the iterator changes too.
|
|
*/
|
|
Private::Iterator i = d->begin();
|
|
while ( i!=d->end() )
|
|
{
|
|
if ( (*i)->isDeleted() || (*i)->isArchived() )
|
|
{
|
|
delete (*i);
|
|
i = d->erase(i);
|
|
}
|
|
else
|
|
{
|
|
++i;
|
|
}
|
|
}
|
|
|
|
// Don't have to do anything. Will be taken care of by closeDatabase()...
|
|
// Changed!
|
|
return 0;
|
|
}
|
|
|
|
TQString PilotLocalDatabase::dbPathName() const
|
|
{
|
|
FUNCTIONSETUP;
|
|
TQString tempName(fPathName);
|
|
TQString slash = CSL1("/");
|
|
|
|
if (!tempName.endsWith(slash)) tempName += slash;
|
|
tempName += getDBName();
|
|
tempName += CSL1(".pdb");
|
|
return tempName;
|
|
}
|
|
|
|
void PilotLocalDatabase::openDatabase()
|
|
{
|
|
FUNCTIONSETUP;
|
|
|
|
pi_file *dbFile;
|
|
|
|
setDBOpen(false);
|
|
|
|
dbFile = pi_file_open( TQFile::encodeName(dbPathName()) );
|
|
if (dbFile == 0L)
|
|
{
|
|
TQString path = dbPathName();
|
|
DEBUGKPILOT << fname << ": Failed to open " << path << endl;
|
|
return;
|
|
}
|
|
|
|
|
|
PI_SIZE_T size = 0;
|
|
void *tmpBuffer;
|
|
pi_file_get_info(dbFile, &fDBInfo);
|
|
pi_file_get_app_info(dbFile, &tmpBuffer, &size);
|
|
fAppLen = size;
|
|
fAppInfo = new char[fAppLen];
|
|
memcpy(fAppInfo, tmpBuffer, fAppLen);
|
|
|
|
int count;
|
|
pi_file_get_entries(dbFile, &count);
|
|
if (count >= 0)
|
|
{
|
|
KPILOT_DELETE(d);
|
|
d = new Private(count);
|
|
}
|
|
|
|
int attr, cat;
|
|
recordid_t id;
|
|
unsigned int i = 0;
|
|
while (pi_file_read_record(dbFile, i,
|
|
&tmpBuffer, &size, &attr, &cat, &id) == 0)
|
|
{
|
|
pi_buffer_t *b = pi_buffer_new(size);
|
|
memcpy(b->data,tmpBuffer,size);
|
|
b->used = size;
|
|
(*d)[i] = new PilotRecord(b, attr, cat, id);
|
|
i++;
|
|
}
|
|
pi_file_close(dbFile); // We done with it once we've read it in.
|
|
|
|
KSaveFile::backupFile( dbPathName() );
|
|
|
|
setDBOpen(true);
|
|
}
|
|
|
|
void PilotLocalDatabase::closeDatabase()
|
|
{
|
|
FUNCTIONSETUP;
|
|
pi_file *dbFile;
|
|
|
|
if (!isOpen())
|
|
{
|
|
DEBUGKPILOT << fname << ": Database " << fDBName
|
|
<< " is not open. Cannot close and write it"
|
|
<< endl;
|
|
return;
|
|
}
|
|
|
|
TQString newName = dbPathName() + CSL1(".new");
|
|
TQString path = dbPathName();
|
|
DEBUGKPILOT << fname
|
|
<< ": Creating temp file " << newName
|
|
<< " for the database file " << path << endl;
|
|
|
|
dbFile = pi_file_create(TQFile::encodeName(newName),&fDBInfo);
|
|
pi_file_set_app_info(dbFile, fAppInfo, fAppLen);
|
|
|
|
for (unsigned int i = 0; i < d->size(); i++)
|
|
{
|
|
// How did a NULL pointer sneak in here?
|
|
if (!(*d)[i])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (((*d)[i]->id() == 0) && ((*d)[i]->isDeleted()))
|
|
{
|
|
// Just ignore it
|
|
}
|
|
else
|
|
{
|
|
pi_file_append_record(dbFile,
|
|
(*d)[i]->data(),
|
|
(*d)[i]->size(),
|
|
(*d)[i]->attributes(), (*d)[i]->category(),
|
|
(*d)[i]->id());
|
|
}
|
|
}
|
|
|
|
pi_file_close(dbFile);
|
|
TQFile::remove(dbPathName());
|
|
rename((const char *) TQFile::encodeName(newName),
|
|
(const char *) TQFile::encodeName(dbPathName()));
|
|
setDBOpen(false);
|
|
}
|
|
|
|
|
|
TQString *PilotLocalDatabase::fPathBase = 0L;
|
|
|
|
void PilotLocalDatabase::setDBPath(const TQString &s)
|
|
{
|
|
FUNCTIONSETUP;
|
|
|
|
DEBUGKPILOT << fname
|
|
<< ": Setting default DB path to "
|
|
<< s
|
|
<< endl;
|
|
|
|
if (!fPathBase)
|
|
{
|
|
fPathBase = new TQString(s);
|
|
}
|
|
else
|
|
{
|
|
*fPathBase = s;
|
|
}
|
|
}
|
|
|
|
/* virtual */ PilotDatabase::DBType PilotLocalDatabase::dbType() const
|
|
{
|
|
return eLocalDB;
|
|
}
|
|
|
|
|
|
/* static */ bool PilotLocalDatabase::infoFromFile( const TQString &path, DBInfo *d )
|
|
{
|
|
FUNCTIONSETUP;
|
|
|
|
pi_file *f = 0L;
|
|
|
|
if (!d)
|
|
{
|
|
return false;
|
|
}
|
|
if (!TQFile::exists(path))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TQCString fileName = TQFile::encodeName( path );
|
|
f = pi_file_open( fileName );
|
|
if (!f)
|
|
{
|
|
WARNINGKPILOT << "Can't open " << path << endl;
|
|
return false;
|
|
}
|
|
|
|
pi_file_get_info(f,d);
|
|
pi_file_close(f);
|
|
|
|
return true;
|
|
}
|
|
|