/* * RecordManager.java */ package gnt; import java.io.*; import java.util.*; import palm.conduit.*; /* Copyright (c) 1997-2001 Palm Inc. or its subsidiaries. All rights reserved. */ /** Performs synchronization of records. This class provides a generic algorithm * that will synchronize records of any type. */ public class RecordManager { SyncProperties props; Vector hhRecords, pcRecords, archivedRecords, backupRecords; Vector slowPCRecords; Class recordClass; int db, recordIndex, recordCount; boolean slowSync; /** Constructor * @param props The SyncProperties for the conduit * @param db The handle for the open database * @param recordClass The Class object representation of the particular Record type * to be synchronized */ public RecordManager(SyncProperties props, int db, Class recordClass){ this.props = props; this.db = db; this.recordClass = recordClass; hhRecords = new Vector(); slowSync = false; } /** Performs slow synchronization of records * @param pcRecords A Vector of records found on the PC * @param archivedRecords A Vector that this method will populate with Archive records * to be stored on the PC * @param backupRecords A Vector of backup records found on the PC */ /* Description: SlowSync is used when the statusFlags can not be relied on for accuracy. If the HH is HotSynced with another PC, then the statusFlags are wiped out. SlowSync uses the Backup file, which is a copy of the file after the last HotSync, to determine which records have been added, changed, or deleted on the HH since the last HotSync. To perform a SlowSync, every record must be read in from the HH, as opposed to the FastSync which reads only the modified records. Procedure: All of the PC records have already been read into memory (into the PC records vector). Likewise all the records from the Backup file are read into a backup vector. For each PC record, if the statusFlag = None, it will be set to Pending. Each HH record will be read in one at a time. Since the HH statusFlags may not be accurate, if the HH statusFlag = None, then that record will be compared against the Backup file record to determine if the HH record has been added, changed, or deleted. Then, each HH record will be compared against the PC vector to determine if the HH record should be added to the PC vector, replace the current PC record, cause the PC record to be deleted from the PC vector, or be added to the Archive file. If the record exists on both the HH and the PC and the PC record has a Pending statusFlag, the statusFlag is changed to its appropriate value. This is important because in the second pass, if the statusFlag = Pending, that means that the record does not exist on the HH and it does exist on the PC with no changes, therefore the record was deleted on the HH so it needs to be deleted from the PC. After each HH record has been read with its sync action performed to the PC table, then a second pass is made to the PC table. For each PC record that is marked as modified (statusFlag != None), a message will be send to the HH to update that HH record. After all the appropriate records are updated on the HH and the statusFlags for each PC record have been cleared, then the PC vector is ready to be written to the PC as the new PC file. */ public Vector slowSyncData(Vector pcRecords, Vector archivedRecords, Vector backupRecords) throws SyncException, IOException{ this.backupRecords = backupRecords; Record hhRecord, pcRecord, record, newRecord; boolean allRecordsRead = false; this.pcRecords = pcRecords; this.archivedRecords = archivedRecords; if (this.pcRecords == null) this.pcRecords = new Vector(); recordIndex = 0; slowPCRecords = new Vector(); slowSync = true; //get record count on the database recordCount = SyncManager.getDBRecordCount(db); for (recordIndex = 0; recordIndex < recordCount; recordIndex++) { hhRecord = getNewRecord(); hhRecord.setIndex(recordIndex); SyncManager.readRecordByIndex(db, hhRecord); // Synchronize the record obtained from the handheld synchronizeHHRecord(hhRecord); } // Copy the new records from the PC to the device for (int i = 0; i < pcRecords.size(); i++) { newRecord = (Record)pcRecords.elementAt(i); if (newRecord.isNew()){ resetAttributes(newRecord); addPCRecord(newRecord); hhRecords.addElement(newRecord); } } writeHHRecords(); //write out the synchronized records to the device. return slowPCRecords; //return the synchronized records from the slowsync. } /** Performs fast synchronization of records * @param pcRecords A Vector of records found on the PC * @param archivedRecords A Vector that this method will populate with Archive records * to be stored on the PC */ /* Description: FastSync reads and writes only the modified records to and from the Handheld. It relies on the statusFlags for each record to be accurate. Procedure: All of the PC records have already been read into memory (into the PC records vector). Each modified HH record will be read in one at a time. Each modified HH record will be compared against the PC vector to determine if the HH record should be added to PC table, replace the current PC record, cause the PC record to be deleted from the PC table, or be added to the Archive file. After each HH record has been read with its sync action performed to the PC vector, then a second pass is made to the PC vector. For each record in the PC vector that is marked as modified (statusFlag != 0), a message will be sent to the HH to update that HH record. After all the appropriate records are updated on the HH and the statusFlags for each PC record have been cleared, the PC vector is ready to be written to the PC as the new file. */ public void fastSyncData(Vector pcRecords, Vector archivedRecords) throws SyncException, IOException{ boolean allRecordsRead = false; Record hhRecord, pcRecord; this.pcRecords = pcRecords; this.archivedRecords = archivedRecords; if (this.pcRecords == null) this.pcRecords = new Vector(); //balance for small data samples // Read in each modified HH record one at a time recordIndex = 0; while (!allRecordsRead) { try{ hhRecord = getNewRecord(); SyncManager.readNextModifiedRec(db, hhRecord); hhRecords.addElement(hhRecord); synchronizeHHRecord(hhRecord); } catch(SyncException e){ allRecordsRead = true; } } allRecordsRead = false; // send each Changed pc record recordIndex = 0; while (!allRecordsRead) { pcRecord = getNextModifiedRecord(); //returns the next modified //pc record if (pcRecord != null){ synchronizePCRecord(pcRecord); resetAttributes(pcRecord); } else allRecordsRead = true; } writeHHRecords(); } /** Synchronizes PC records one at a time. This method should only be called after all hand-held * records have been synchronized. * @param pcRecord Record object to the synchronized */ public void synchronizePCRecord(Record pcRecord) throws IOException, SyncException{ // assumption here is that we will only be looking at records that are changed on // the PC side. All hand-held changes have been synced by now Record hhRecord; if (!pcRecord.isNew()) { hhRecord = retrieveHHRecord(pcRecord); if (hhRecord == null) { // record does not exist // then add record to hand-held // the id on the pc record will be populated with the device id if (props.firstDevice == 2){ //check if the device was just reset, //in which case all lone pc records are copied to the device hhRecords.addElement(pcRecord); } SyncManager.writeRec(db, pcRecord); } else { if (pcRecord.isArchived()) { archiveRecord(pcRecord); deletePCRecord(pcRecord); deleteHHRecord(pcRecord); } else if (pcRecord.isDeleted()) { deletePCRecord(pcRecord); deleteHHRecord(hhRecord); } else { // modified, ... overwrite hand-held rec if (!compareRecords(hhRecord, pcRecord)) { // records are different hhRecords.addElement(pcRecord); } } } //else pc record is new } else if (pcRecord.isArchived()) { archiveRecord(pcRecord); deletePCRecord(pcRecord); } else if (pcRecord.isDeleted()) { deletePCRecord(pcRecord); } else { // modified, ... overwrite hand-held rec resetAttributes(pcRecord); hhRecords.addElement(pcRecord); } } /** Synchronizes hand-held records one at a time. * @param hhRecord Record object to the synchronized */ public void synchronizeHHRecord(Record hhRecord) throws SyncException, IOException{ Record pcRecord; // Check to see if PC Record does not exist with matching Record ID. pcRecord = getRecordById(hhRecord.getId(), pcRecords); if (pcRecord == null){ // if there is no pc rec with the matching RecID if (hhRecord.isArchived()){ archiveRecord(hhRecord); deleteHHRecord(hhRecord); } else if (hhRecord.isDeleted()){ deletePCRecord(hhRecord); deleteHHRecord(hhRecord); } else{ //default //reset the attribute flags for the record resetAttributes(hhRecord); //first add it to the pc records Vector addPCRecord(hhRecord); hhRecords.removeElement(hhRecord); } } else { if (hhRecord.isArchived()){ handleArchived(hhRecord, pcRecord); } else if (hhRecord.isDeleted()){ handleDeleted(hhRecord, pcRecord); } else handleModified(hhRecord, pcRecord); } } /** Synchronizes hand-held records that are marked as archived with counterpart PC record. * @param hhRecord Hand-held Record object to the synchronized * @param pcRecord PC record object with an Id which equals the hand-held record */ public void handleArchived(Record hhRecord, Record pcRecord) throws IOException{ Record backupRecord; if (!pcRecord.isModified()) { // pc record has not changed // Add the HH record to the archive table archiveRecord(hhRecord); deleteHHRecord(hhRecord); //ADDED(??) // Mark as already deleted from the HH and needs to // be deleted from the PC deletePCRecord(pcRecord); return; } else if (!hhRecord.isModified() && !hhRecord.isNew() && !hhRecord.isDeleted() && props.syncType == SyncProperties.SYNC_SLOW) { // SLOWSYNC // HH rec = No change // Only look at backup record during a slow sync // Compare HH record to backup file and mark HH // record as modified if records are not identical backupRecord = getRecordById(hhRecord.getId(), backupRecords); if (backupRecord == null || !compareRecords(hhRecord, backupRecord)){ // if record does not exist in the backup table or it doesn't match backup record hhRecord.setIsModified(true); } } if (hhRecord.isModified()) { // Both records have changed if (compareRecords(hhRecord, pcRecord)) {// records are identical // Add the HH record to the archive table archiveRecord(hhRecord); deleteHHRecord(hhRecord); // Mark as already deleted from the HH and needs to // be deleted from the PC deletePCRecord(pcRecord); } else {// records are different // Change the PC record to a new record so // that it gets added to the HH in the second pass thru the pc records pcRecord.setId(0); pcRecord.setIsNew(true); // Disallow the original Archive action, keep HH record. resetAttributes(hhRecord); // Add the device record to the pc Vector addPCRecord(hhRecord); } } else{ if (pcRecord.isArchived() || pcRecord.isDeleted()){ //Archive the HH record, delete the PC record archiveRecord(hhRecord); deletePCRecord(pcRecord); deleteHHRecord(hhRecord); } else{ // (HH = Archive and PC = Modified) causes HH record to be // updated not archived resetAttributes(pcRecord); deleteHHRecord(hhRecord); hhRecords.addElement(pcRecord); } } } /** Synchronizes hand-held records that are marked as modified with counterpart PC record. * @param hhRecord Hand-held Record object to the synchronized * @param pcRecord PC record object with an Id which equals the hand-held record */ public void handleModified(Record hhRecord, Record pcRecord) throws IOException{ // Record exists on both HH and PC // HH record status can only be None (Not Modified) during SlowSync // because only the modified records are retrieved // during a FastSync Record backupRecord; if (!hhRecord.isModified()){ // Compare HH record to backup file and mark HH // record as modified if records are not identical backupRecord = getRecordById(hhRecord.getId(), backupRecords); // if record does not exist in the backup table if (backupRecord == null || !compareRecords(hhRecord, backupRecord)){ // if record does not exist in the backup table or HH record is different from backup record hhRecord.setIsModified(true); } } if (hhRecord.isModified()) { // HH record has been changed if (pcRecord.isDeleted()){ //copy the hh record onto the PC and deleted the old PC record deletePCRecord(pcRecord); addPCRecord(hhRecord); } else if (!pcRecord.isModified()) {// PC record has not changed // but may be deleted, archived // Replace the PC record with the HH record resetAttributes(hhRecord); deletePCRecord(pcRecord); addPCRecord(hhRecord); } else if (compareRecords(hhRecord, pcRecord)) { // both records have changed identically if (pcRecord.isArchived()) { // Add the PC record to the archive table archiveRecord(pcRecord); deletePCRecord(pcRecord); deleteHHRecord(hhRecord); } else if (pcRecord.isDeleted()) { deletePCRecord(pcRecord); deleteHHRecord(hhRecord); } else { resetAttributes(pcRecord); if (slowSync){ slowPCRecords.addElement(pcRecord); } } } else { // records are different // Change the PC record to a new record so // that it gets added to the HH on pass thru pc records resetAttributes(pcRecord); pcRecord.setIsNew(true); pcRecord.setId(0); resetAttributes(hhRecord); // Add the HH record to the PC table addPCRecord(hhRecord); } } else { //SlowSync: if the hand-held is not modified, process with the pc record //then put the pc record into the slowPCRecords Vector. What will //be left in the pcRecords Vector are all the pc records deleted from the device if (pcRecord.isArchived()) { archiveRecord(pcRecord); //deletePCRecord(pcRecord); deleteHHRecord(pcRecord); } else if (pcRecord.isDeleted()) { //deletePCRecord(pcRecord); deleteHHRecord(hhRecord); } else if (pcRecord.isModified() && !compareRecords(hhRecord, pcRecord)) { // records are different hhRecords.addElement(pcRecord); //overwrite hand-held record slowPCRecords.addElement(pcRecord); } else slowPCRecords.addElement(pcRecord); pcRecords.removeElement(pcRecord); } } /** Synchronizes hand-held records that are marked as deleted with counterpart PC record. * @param hhRecord Hand-held Record object to the synchronized * @param pcRecord PC record object with an Id which equals the hand-held record */ public void handleDeleted(Record hhRecord, Record pcRecord) throws IOException{ // if record exists on the PC if (pcRecord.isArchived()) { // Add the PC record to the archive table archiveRecord(pcRecord); deleteHHRecord(hhRecord); // Marked as already deleted from the HH and needs // to be deleted from the PC deletePCRecord(pcRecord); } else if (pcRecord.isModified()) { // (HH = Delete and PC = Modified) causes HH record to be updated not deleted resetAttributes(pcRecord); hhRecords.addElement(pcRecord); } else { // Marked as already deleted from the HH and needs // to be deleted from the PC deleteHHRecord(hhRecord); deletePCRecord(pcRecord); } } /** Copies the entire list of hand-held records to a Vector which can be used to write * the records to PC text files. This method can be called when the PC text files * are empty and all device records should be copied to the PC. * @return A Vector containing all the record objects from the hand-held device. */ public Vector copyHHRecords() throws SyncException, IOException{ Record hhRecord; pcRecords = new Vector(); //get record count on the database int recordCount = SyncManager.getDBRecordCount(db); for (recordIndex = 0; recordIndex < recordCount; recordIndex++) { hhRecord = getNewRecord(); hhRecord.setIndex(recordIndex); SyncManager.readRecordByIndex(db, hhRecord); // Synchronize the record obtained from the handheld if (!hhRecord.isDeleted()){ resetAttributes(hhRecord); addPCRecord(hhRecord); } } SyncManager.purgeDeletedRecs(db); //deletes all hh records marked as deleted SyncManager.resetSyncFlags(db); //reset all the sync flags on the hh return pcRecords; } /** Copies the entire list of PC records to the hand-held device. * Any records previously existing on the device are deleted. * @return A Vector containing all the record objects from the PC that have been * updated with new IDs and flags from the device. */ public Vector copyPCRecords(Vector pcRecs) throws SyncException, IOException { pcRecords = pcRecs; hhRecords = pcRecords; int recordCount = pcRecords.size(); SyncManager.purgeAllRecs(db); // blow away all records on hand held. writeHHRecords(); // write all PC records to HH pcRecords = copyHHRecords(); // HH may have reassigned ID#'s &Indices. Flags have been reset. return pcRecords; } private Record getRecordById(int id, Vector records){ Record tempRecord; if (records != null){ for (int i = 0; i < records.size(); i++){ tempRecord = (Record)records.elementAt(i); if (tempRecord.getId() == id){ return tempRecord; } } } return null; } private void archiveRecord(Record record) throws SyncException{ resetAttributes(record); archivedRecords.addElement(record); } private void deleteHHRecord(Record record) throws SyncException{ SyncManager.deleteRecord(db, record); //remove the hand-held record from the vector of hand-held records hhRecords.removeElement(record); //reduce the count of hand-held records and read the same record index again recordCount--; recordIndex--; } private Record retrieveHHRecord(Record pcRecord) throws IOException{ Record hhRecord = getNewRecord(); // determine if res or resource hhRecord.setId(pcRecord.getId()); try { SyncManager.readRecordById(db, hhRecord); } catch(SyncException e){ //hand-held record does not exist return null; } return hhRecord; } private void addPCRecord(Record record){ if (slowSync){ slowPCRecords.addElement(record); } else{ pcRecords.addElement(record); } } private void deletePCRecord(Record pcRecord){ pcRecords.removeElement(pcRecord); } private void resetAttributes(Record record){ record.setIsModified(false); record.setIsArchived(false); record.setIsDeleted(false); record.setIsNew(false); } private Record getNextModifiedRecord(){ Record record; //search thru the pcRecord Vector looking for the next modified record if (pcRecords != null){ for (int i = recordIndex; i < pcRecords.size(); i++){ record = (Record)pcRecords.elementAt(i); recordIndex++; if (props.firstDevice == 2 || record.isModified() || record.isNew() || record.isDeleted() || record.isArchived()){ return record; } } } return null; } private Record getNewRecord(){ Record hhRecord = null; try { hhRecord = (Record)recordClass.newInstance(); } catch (Throwable t) { System.out.println("Uncaught exception: " + t); t.printStackTrace(); } return hhRecord; } private void writeHHRecords() throws IOException{ Record hhRecord; if (hhRecords != null){ for(int i = 0; i < hhRecords.size(); i++){ hhRecord = (Record)hhRecords.elementAt(i); SyncManager.writeRec(db, hhRecord); } SyncManager.purgeDeletedRecs(db); //deletes all hh records marked as deleted SyncManager.resetSyncFlags(db); //reset all the sync flags on the hh } } private boolean compareRecords(Record firstRecord, Record secondRecord) throws IOException{ if (//firstRecord.getCategoryIndex() != secondRecord.getCategoryIndex() || firstRecord.isPrivate() != secondRecord.isPrivate()){ return false; } return firstRecord.equals(secondRecord); } }