/********************************************************************************* * TotalCross Software Development Kit * * Copyright (C) 2000-2012 SuperWaba Ltda. * * All Rights Reserved * * * * This library and virtual machine 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. * * * * This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 * * A copy of this license is located in file license.txt at the root of this * * SDK or can be downloaded here: * * http://www.gnu.org/licenses/lgpl-3.0.txt * * * *********************************************************************************/ package totalcross.io.sync; import totalcross.io.*; import totalcross.sys.Convert; /** This class represents a Palm DataBase that is being synchronized. */ /** * Allows you to access a pdb file on the device from the desktop during the conduit synchronization.<br> * On PalmOS it may only be used to handle files on the device's internal memory. */ public final class RemotePDBFile { protected String name; // guich@572_4: made all fields protected so they are not removed by the obfuscator. protected int mode; protected boolean open; protected int recIndex = -1; protected int wRecordSize; protected int lastSearchedRec = -1; boolean dontFinalize; Object pdbHandle; private static boolean idle = true; private static ByteArrayStream rwbas = new ByteArrayStream(65500); // the buffer used on read and write operations - max possible size private static DataStream ds = new DataStream(rwbas); public static int RECORD_SIZE_AUTO = 0; /** * Opens a remote PDBFile with the given name. Important: only one PDBFile * can be open at a time. Trying to open a new PDBFile before explicitly * closing the former will throw a RuntimeException. Note that the * Settings.dataPath, if set, is used by this method as the path to the * PDBFile. * * @param name * The PDBFile name on the form <code>name.crtr.type</code> * @param mode * Can be PDBFile.READ_WRITE, PDBFile.CREATE (this one is non * destructive: if the PDBFile don't exists, it will be created and * it will remain open on READ_WRITE mode; if it exists, it will * stay open on READ_WRITE mode). You can also or the mode with * PDBFile.DB_ATTR_BACKUP or PDBFile.DB_ATTR_STREAM to set these * attributes when creating a database (do NOT use the other * attributes!) * @param recordSize * Used only when writing records. If you plan to write records * with fixed size, pass in the desired size. Otherwise, pass * RemotePDBFile.RECORD_SIZE_AUTO to automatically expand or shrink * the record. * @throws totalcross.io.IOException * @throws totalcross.io.FileNotFoundException * @throws totalcross.io.IllegalArgumentIOException */ public RemotePDBFile(String name, int mode, int recordSize) throws totalcross.io.IllegalArgumentIOException, totalcross.io.FileNotFoundException, totalcross.io.IOException { if (!idle) throw new RuntimeException("Only one database can be open at a time!"); if (name == null) throw new java.lang.NullPointerException("Argument 'name' cannot have a null value."); String[] st = Convert.tokenizeString(name, '.'); if (st == null || st.length != 3 || st[0].length() > 31 || st[1].length() != 4 || st[2].length() != 4) throw new totalcross.io.IllegalArgumentIOException("Invalid value for argument 'name' " + name); this.name = name; this.mode = mode; this.wRecordSize = recordSize; create(); idle = !open; // this is ok: open will be changed by the native method. } private void create() throws totalcross.io.IllegalArgumentIOException, totalcross.io.FileNotFoundException, totalcross.io.IOException { pdbHandle = new PDBFile(name, mode); open = true; } native void create4D() throws totalcross.io.IllegalArgumentIOException, totalcross.io.FileNotFoundException, totalcross.io.IOException; /** * Opens a remote PDBFile in READ_WRITE mode. * * @throws totalcross.io.IOException * @throws totalcross.io.FileNotFoundException * @throws totalcross.io.IllegalArgumentIOException * * * @see #RemotePDBFile(String, int, int) */ public RemotePDBFile(String name) throws totalcross.io.IllegalArgumentIOException, totalcross.io.FileNotFoundException, totalcross.io.IOException { this(name, PDBFile.READ_WRITE, 0); } /** * Opens a remote PDBFile in the given mode. * * @throws totalcross.io.IOException * @throws totalcross.io.FileNotFoundException * @throws totalcross.io.IllegalArgumentIOException * * * @see #RemotePDBFile(String, int, int) */ public RemotePDBFile(String name, int mode) throws totalcross.io.IllegalArgumentIOException, totalcross.io.FileNotFoundException, totalcross.io.IOException { this(name, mode, 0); } /** * Completely deletes this database, closing it first. * * @throws totalcross.io.IOException */ final public void delete() throws totalcross.io.IOException { ((PDBFile) pdbHandle).delete(); idle = true; } native public void delete4D() throws totalcross.io.IOException; /** * Returns the number of records inside this database * * @throws totalcross.io.IOException */ final public int getRecordCount() throws totalcross.io.IOException { return ((PDBFile) pdbHandle).getRecordCount(); } native public int getRecordCount4D() throws totalcross.io.IOException; /** * Fetches the given index, passing to the rec a DataStream to read the * record information. * * @throws totalcross.io.IOException * @throws totalcross.io.IllegalArgumentIOException */ public boolean readRecord(int index, RemotePDBRecord rec) throws totalcross.io.IllegalArgumentIOException, totalcross.io.IOException { rec.rc = this; // assign fields to the custom record if ((rec.size = rwRecord(index, rwbas, true)) > 0) // fetch and fill our buffer { // fill in the record. rwbas is already reseted by native counterpart. rec.read(ds); return true; } return false; } /** * Write the record at the given index. <b>IMPORTANT</b>: if index is -1, * the record is appended, otherwise, the given record is OVERWRITTEN. It is * not possible to insert a record into a given position due to limitations * of the native API. Also, passing a record index greater than the number of * records may cause unexpected results. * * @throws totalcross.io.IOException * @throws totalcross.io.IllegalArgumentIOException */ public boolean writeRecord(int index, RemotePDBRecord rec) throws totalcross.io.IllegalArgumentIOException, totalcross.io.IOException { rwbas.reset(); // reset the buffer rec.write(ds); // write to it return rwRecord(index, rwbas, false) > 0; // send it to the pda } /** * return the PDBFile's record size * * @throws totalcross.io.IOException * @throws totalcross.io.IllegalArgumentIOException */ int rwRecord(int idx, ByteArrayStream bas, boolean read) throws totalcross.io.IllegalArgumentIOException, totalcross.io.IOException { recIndex = idx; PDBFile pdbFile = ((PDBFile) pdbHandle); if (read) { pdbFile.setRecordPos(idx); int size = pdbFile.getRecordSize(); bas.setSize(size, false); pdbFile.readBytes(bas.getBuffer(), 0, size); return size; } else { int size = bas.getPos(); if (idx < 0) pdbFile.addRecord(size); else { pdbFile.setRecordPos(idx); pdbFile.resizeRecord(size); } // if ((idx < 0 && >= 0) || (idx >= 0 && cat.setRecordPos(idx) && cat.resizeRecord(size))) { pdbFile.writeBytes(bas.getBuffer(), 0, size); // guich@570_14: reset the dirty flag pdbFile.setRecordAttributes(idx, (byte) (pdbFile.getRecordAttributes(idx) & ~PDBFile.REC_ATTR_DIRTY)); return size; } } } native int rwRecord4D(int idx, totalcross.io.ByteArrayStream bas, boolean read); /** Returns the current record position or -1 if there is no current record. */ public int getRecordPos() { return recIndex; } /** * Deletes the given record index. The record is immediately removed from the * PDBFile and all subsequent records are moved up one position. IMPORTANT: * due to this behaviour, if you plan to delete all records from a database, * it is <b>much faster</b> if you delete them in REVERSE ORDER (from last * to first). If you plan to delete all records, delete the whole database * using <code>delete</code> and then create it again. * * @throws totalcross.io.IOException * @throws totalcross.io.IllegalArgumentIOException */ final public void deleteRecord(int index) throws totalcross.io.IllegalArgumentIOException, totalcross.io.IOException { ((PDBFile) pdbHandle).setRecordPos(index); ((PDBFile) pdbHandle).deleteRecord(); } native public void deleteRecord4D(int index) throws totalcross.io.IllegalArgumentIOException, totalcross.io.IOException; /** * Moves the cursor n bytes from the current position, moving backwards if n is negative, or forward if n is * positive.<br> * The cursor cannot be placed outside the stream limits, stopping at position 0 when moving backwards, or at the * last position of the stream, when moving forward. * * @param n * the number of bytes to move. * @return the number of bytes actually moved. */ public int skipBytes(int n) { return rwbas.skipBytes(n); } /** * Returns the next modified record index. Can be used with the readRecord to * only retrieve the changed records since the last synchronization. * * @throws totalcross.io.IOException */ final public int getNextModifiedRecordIndex() throws totalcross.io.IOException { PDBFile pdbFile = ((PDBFile) pdbHandle); int n = pdbFile.getRecordCount(); for (int i = pdbFile.getRecordPos(); i < n; i++) if ((pdbFile.getRecordAttributes(i) & PDBFile.REC_ATTR_DIRTY) != 0) // guich@570_13: replaced by the correct record attribute return i; return -1; } native public int getNextModifiedRecordIndex4D(); /** * Closes this PDBFile. * * @throws totalcross.io.IOException */ final public void close() throws totalcross.io.IOException { idle = true; // guich@tc114_95: set it here before close, because if close throws an exception the user will never be able to sync again. ((PDBFile) pdbHandle).close(); } native public void close4D() throws totalcross.io.IOException; /** * Lists the available PDBFiles on the device that has the given creator id * and type. If you don't pass a creator id and/or a type, this method will * return with NO RESULTS. Note that the Settings.dataPath, if set, is used * by this method as the path to the PDBFiles. * * @return A String array with the answer, or null if no db was found that * matched the criteria. */ public static String[] listPDBs(int crtr, int type) { // get the list String[] list = PDBFile.listPDBs(crtr, type); // now strip the first _ so that the user gets the expected name of a RemotePDBFile if (list != null) for (int i = list.length - 1; i >= 0; i--) if (list[i].charAt(0) == '_') list[i] = list[i].substring(1); return list; } native public static String[] listPDBs4D(int crtr, int type); protected void finalize() { try { close(); } catch (totalcross.io.IOException e) { } } }