From a4bab69edfb5f3f0bc159e0d260f44107ddaa7fd Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Fri, 9 Apr 2010 17:58:48 +1000 Subject: [PATCH] Working implementation of ical detatched events manager, can get split events from series into phone --- .../bionicmessage/objects/AbstractObjectStore.java | 3 +- .../MultipleSourceICalendarObjectStore.java | 68 +++++++++--- .../net/bionicmessage/objects/ObjectDatabase.java | 82 ++++++++++++--- .../bionicmessage/objects/RecurrenceManager.java | 116 +++++++++++++++++++++ 4 files changed, 241 insertions(+), 28 deletions(-) create mode 100644 src/main/java/net/bionicmessage/objects/RecurrenceManager.java diff --git a/src/main/java/net/bionicmessage/objects/AbstractObjectStore.java b/src/main/java/net/bionicmessage/objects/AbstractObjectStore.java index 3cadb80..7c9437c 100644 --- a/src/main/java/net/bionicmessage/objects/AbstractObjectStore.java +++ b/src/main/java/net/bionicmessage/objects/AbstractObjectStore.java @@ -83,6 +83,7 @@ public abstract class AbstractObjectStore { ArrayList updatedOnServer = new ArrayList(); ArrayList deletedFromServer = new ArrayList(); Hashtable sources = new Hashtable(); + Map virtualObjects = null; Logger log = null; FileHandler fh = null; int serverMode = 0; @@ -343,7 +344,7 @@ public abstract class AbstractObjectStore { } } - + this.virtualObjects = this.db.getVirtualObjectMap(); this.log.info("Sync finished"); } diff --git a/src/main/java/net/bionicmessage/objects/MultipleSourceICalendarObjectStore.java b/src/main/java/net/bionicmessage/objects/MultipleSourceICalendarObjectStore.java index 79c2bb0..3dcdfa4 100644 --- a/src/main/java/net/bionicmessage/objects/MultipleSourceICalendarObjectStore.java +++ b/src/main/java/net/bionicmessage/objects/MultipleSourceICalendarObjectStore.java @@ -24,7 +24,6 @@ */ package net.bionicmessage.objects; -import java.io.PrintStream; import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; @@ -33,14 +32,12 @@ import java.sql.Timestamp; import java.util.ArrayList; import java.util.Hashtable; import java.util.List; -import java.util.logging.Logger; import net.bionicmessage.groupdav.CalDAVFormatter; import net.bionicmessage.groupdav.GroupDAVObject; import net.bionicmessage.groupdav.util.icalUtilities; import net.bionicmessage.utils.QPDecode; import net.fortuna.ical4j.data.CalendarBuilder; import net.fortuna.ical4j.model.Component; -import net.fortuna.ical4j.model.ComponentList; import net.fortuna.ical4j.model.property.Summary; import net.fortuna.ical4j.model.property.Uid; @@ -117,7 +114,16 @@ public class MultipleSourceICalendarObjectStore extends AbstractObjectStore { de = new Timestamp(0L); } this.db.createObject(storeName, uid.getValue(), obj.getLocation(), obj.getEtag(), summ.getValue().trim(), cd, ds, de); - + // Compile a list of recurrences + List recurrenceIds = RecurrenceManager.getDetatchedRecurrenceUids(cd); + String[] virtual_guids = new String[recurrenceIds.size()]; + String[] vids = new String[recurrenceIds.size()]; + recurrenceIds.toArray(vids); + for(int i=0; i oldRids = this.db.getVIDs(uid); + List thisRids = RecurrenceManager.getDetatchedRecurrenceUids(cd); + for(String s: thisRids) { + if (oldRids.contains(s)) { + oldRids.remove(s); + this.updatedInStore.add(uid.concat("-r").concat(s)); + } else { + this.addedToStore.add(uid.concat("-r").concat(s)); + } + } + for(String deleted: oldRids) { + this.deletedFromStore.add(uid.concat("-r").concat(deleted)); + } + this.db.deleteVirtualObjects(oldRids); } + /** Search for existing UIDs with a specified name (summary), start time + * and end time + * @param name Object name (iCal SUMMARY) + * @param dstime Start time in milliseconds + * @param detime End time in milliseconds + * @return A matching UID, or null if not found + * @throws Exception + */ public String searchUids(String name, long dstime, long detime) throws Exception { Timestamp ms = new Timestamp(dstime); this.jcal.setTime(ms); @@ -166,20 +196,28 @@ public class MultipleSourceICalendarObjectStore extends AbstractObjectStore { } public net.fortuna.ical4j.model.Calendar getObjectFromStore(String uid) throws ObjectStoreException { - if (this.objCache.get(uid) != null) { + boolean isVirtualObject = this.virtualObjects.containsKey(uid); + String rootObjectUid = (isVirtualObject) ? this.virtualObjects.get(uid) : uid; + net.fortuna.ical4j.model.Calendar masterCal = null; + if (this.objCache.get(rootObjectUid) != null) { this.log.fine("Object " + uid + "has been retrieved from store"); - net.fortuna.ical4j.model.Calendar cal = (net.fortuna.ical4j.model.Calendar) this.objCache.get(uid); - return cal; - } - try { - net.fortuna.ical4j.model.Calendar c = (net.fortuna.ical4j.model.Calendar) this.db.getObjectByUid(uid); - if (c == null) { - throw new ObjectStoreException(uid, ObjectStoreException.OP_NOTSPECIFIED); + masterCal = (net.fortuna.ical4j.model.Calendar) this.objCache.get(rootObjectUid); + } else { + try { + masterCal = (net.fortuna.ical4j.model.Calendar) this.db.getObjectByUid(uid); + if (masterCal == null) { + throw new ObjectStoreException(uid, ObjectStoreException.OP_NOTSPECIFIED); + } + } catch (SQLException ex) { + throw new ObjectStoreException(uid, ObjectStoreException.OP_NOTSPECIFIED, new byte[0], ex); } - return c; - } catch (SQLException ex) { - throw new ObjectStoreException(uid, ObjectStoreException.OP_NOTSPECIFIED, new byte[0], ex); } + if (isVirtualObject) { + String rId = uid.split("-r")[1]; + net.fortuna.ical4j.model.Calendar r = RecurrenceManager.obtainStandaloneRecurrence(masterCal, rId); + return r; + } + return RecurrenceManager.obtainMasterEvent(masterCal); } private net.fortuna.ical4j.model.Calendar constructCalendarObject(byte[] content) diff --git a/src/main/java/net/bionicmessage/objects/ObjectDatabase.java b/src/main/java/net/bionicmessage/objects/ObjectDatabase.java index fe04c2a..f694843 100644 --- a/src/main/java/net/bionicmessage/objects/ObjectDatabase.java +++ b/src/main/java/net/bionicmessage/objects/ObjectDatabase.java @@ -61,6 +61,11 @@ public class ObjectDatabase { private static final String GET_UID_FROM_NAME = "SELECT obj_uid FROM groupdav_objects WHERE obj_name LIKE ?"; private static final String GET_UIDS_WHERE_URL_IN_ARRAY_BASE = "SELECT obj_uid, obj_url FROM groupdav_objects WHERE obj_url IN ("; + private static final String CREATE_VIRTUAL_OBJECTS = "CREATE TABLE virtual_objects ( " + + "virtual_guid VARCHAR(255)," + + "virtual_id VARCHAR(255)," + + "parent_id VARCHAR(255))"; + public ObjectDatabase(String path) { this.dbpath = new File(path); this.dbpath.mkdirs(); @@ -100,17 +105,21 @@ public class ObjectDatabase { protected void createTables() throws SQLException { Statement createObjectsTable = this.conn.createStatement(); - createObjectsTable.execute("CREATE TABLE groupdav_objects (obj_source varchar(48),obj_url varchar(255),obj_etag varchar(255),obj_uid varchar(255),obj_name varchar(255),obj_dtstart timestamp,obj_dtend timestamp,obj_content other,UNIQUE(obj_url),UNIQUE(obj_uid));"); + createObjectsTable.execute(CREATE_OBJECTS_SQL); createObjectsTable.close(); Statement createObjectsUidIndex = this.conn.createStatement(); - createObjectsUidIndex.execute("CREATE INDEX url_index ON groupdav_objects(obj_url);"); + createObjectsUidIndex.execute(CREATE_OBJECTS_UID_INDEX); createObjectsUidIndex.close(); + + Statement createVirtualTable = this.conn.createStatement(); + createVirtualTable.execute(CREATE_VIRTUAL_OBJECTS); + createVirtualTable.close(); } public void createObject(String sourceName, String uid, String url, String etag, String name, Object contents, Timestamp dtstart, Timestamp dtend) throws Exception { - PreparedStatement ps = this.conn.prepareStatement("INSERT INTO groupdav_objects (obj_source, obj_url, obj_etag, obj_uid, obj_name, obj_dtstart, obj_dtend, obj_content) VALUES (?,?,?,?,?,?,?,?);"); + PreparedStatement ps = this.conn.prepareStatement(INSERT_INTO_OBJECTS_QUERY); ps.setString(1, sourceName); ps.setString(2, url); ps.setString(3, etag); @@ -133,7 +142,7 @@ public class ObjectDatabase { public Object getObjectByUid(String uid) throws SQLException { - PreparedStatement getStatement = this.conn.prepareStatement("SELECT obj_content FROM groupdav_objects WHERE obj_uid = ?"); + PreparedStatement getStatement = this.conn.prepareStatement(GET_OBJECT_UID_QUERY); getStatement.setString(1, uid); getStatement.execute(); @@ -154,7 +163,7 @@ public class ObjectDatabase { } public Map getUrlEtagMapForStore(String store) throws SQLException { - PreparedStatement stmt = this.conn.prepareStatement("SELECT obj_url, obj_etag FROM groupdav_objects WHERE obj_source = ?"); + PreparedStatement stmt = this.conn.prepareStatement(GET_URL_ETAG_FROM_STORE_QUERY); stmt.setString(1, store); stmt.execute(); @@ -171,7 +180,7 @@ public class ObjectDatabase { public String getObjectURL(String uid) throws SQLException { - PreparedStatement stmt = this.conn.prepareStatement("SELECT obj_url FROM groupdav_objects WHERE obj_uid = ?"); + PreparedStatement stmt = this.conn.prepareStatement(GET_URL_FOR_UID_QUERY); stmt.setString(1, uid); stmt.execute(); ResultSet rs = stmt.getResultSet(); @@ -186,7 +195,7 @@ public class ObjectDatabase { public void updateObject(String uid, String newEtag, String newName, Timestamp dtstart, Timestamp dtend, Object contents) throws SQLException { - PreparedStatement stmt = this.conn.prepareStatement("UPDATE groupdav_objects SET obj_etag = ?, obj_name = ?,obj_dtstart = ?, obj_dtend = ?, obj_content = ? WHERE obj_uid = ?"); + PreparedStatement stmt = this.conn.prepareStatement(UPDATE_ETAG_NAME_DATE_QUERY); stmt.setString(1, newEtag); stmt.setString(2, newName); if (dtstart == null) { @@ -206,7 +215,7 @@ public class ObjectDatabase { } public String getEtagForUid(String uid) throws SQLException { - PreparedStatement stmt = this.conn.prepareStatement("SELECT obj_etag FROM groupdav_objects WHERE obj_uid = ?"); + PreparedStatement stmt = this.conn.prepareStatement(GET_ETAG_FROM_UID_QUERY); stmt.setString(1, uid); stmt.execute(); ResultSet rs = stmt.executeQuery(); @@ -221,7 +230,7 @@ public class ObjectDatabase { public String[] getUrlEtagForUid(String uid) throws SQLException { - PreparedStatement stmt = this.conn.prepareStatement("SELECT obj_url, obj_etag FROM groupdav_objects WHERE obj_uid = ?"); + PreparedStatement stmt = this.conn.prepareStatement(GET_URL_ETAG_FROM_UID_QUERY); stmt.setString(1, uid); stmt.execute(); ResultSet rs = stmt.executeQuery(); @@ -261,9 +270,9 @@ public class ObjectDatabase { throws SQLException { PreparedStatement stmt = null; if ((dtstart != null) && (dtend != null)) { - stmt = this.conn.prepareStatement("SELECT obj_uid FROM GROUPDAV_OBJECTS WHERE obj_name LIKE ? AND obj_dtstart = ? AND obj_dtend = ? "); + stmt = this.conn.prepareStatement(GET_UID_FROM_NAME_START_END); } else { - stmt = this.conn.prepareStatement("SELECT obj_uid FROM groupdav_objects WHERE obj_name LIKE ?"); + stmt = this.conn.prepareStatement(GET_UID_FROM_NAME); } stmt.setString(1, name); if ((dtstart != null) && (dtend != null)) { @@ -294,7 +303,7 @@ public class ObjectDatabase { } public Map getUIDsForURLs(List urls) throws SQLException { - StringBuffer inBuf = new StringBuffer("SELECT obj_uid, obj_url FROM groupdav_objects WHERE obj_url IN ("); + StringBuffer inBuf = new StringBuffer(GET_UIDS_WHERE_URL_IN_ARRAY_BASE); for (int i = 0; i < urls.size(); ++i) { String s = (String) urls.get(i); @@ -331,4 +340,53 @@ public class ObjectDatabase { this.itemCount = 100; } } + public void insertVirtualObjects(String parentId, String[] vguid, String[] vid) throws SQLException { + PreparedStatement stmt = this.conn.prepareStatement("insert into virtual_objects (virtual_guid ,virtual_id ,parent_id ) VALUES (?,?,?)"); + if (vguid.length == 0) + return; + + for (int i = 0; i < vid.length; i++) { + stmt.setString(1, vguid[i]); + stmt.setString(2, vid[i]); + stmt.setString(3, parentId); + stmt.addBatch(); + } + stmt.executeBatch(); + stmt.close(); + } + public List getVIDs(String parentId) throws SQLException { + PreparedStatement stmt = this.conn.prepareStatement("SELECT virtual_id FROM virtual_objects WHERE parent_id = ?"); + stmt.setString(1, parentId); + ResultSet rs = stmt.executeQuery(); + ArrayList vids = new ArrayList(); + while(rs.next()) { + vids.add(rs.getString("virtual_id")); + } + stmt.close(); + return vids; + } + public void deleteVirtualObjects(List vids) throws SQLException { + if (vids.size() == 0) { + return; + } + PreparedStatement stmt = this.conn.prepareStatement("DELETE FROM virtual_objects WHERE virtual_id = ?"); + for(String vid: vids) { + stmt.setString(1, vid); + stmt.addBatch(); + } + stmt.executeBatch(); + stmt.close(); + } + + public Map getVirtualObjectMap() throws SQLException { + HashMap map = new HashMap(); + Statement stmt = this.conn.createStatement(); + stmt.execute("SELECT virtual_guid, parent_id FROM virtual_objects"); + ResultSet rs = stmt.getResultSet(); + while(rs.next()) { + map.put(rs.getString("virtual_guid"),rs.getString("parent_id")); + } + stmt.close(); + return map; + } } diff --git a/src/main/java/net/bionicmessage/objects/RecurrenceManager.java b/src/main/java/net/bionicmessage/objects/RecurrenceManager.java new file mode 100644 index 0000000..55e5872 --- /dev/null +++ b/src/main/java/net/bionicmessage/objects/RecurrenceManager.java @@ -0,0 +1,116 @@ +/* + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY Mathew McBride ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of Mathew McBride. + */ +package net.bionicmessage.objects; + +import java.util.ArrayList; +import java.util.List; +import net.fortuna.ical4j.model.Calendar; +import net.fortuna.ical4j.model.Component; +import net.fortuna.ical4j.model.ComponentList; +import net.fortuna.ical4j.model.Date; +import net.fortuna.ical4j.model.property.ExDate; +import net.fortuna.ical4j.model.property.RecurrenceId; +import net.fortuna.ical4j.model.property.Uid; + +/** + * + * @author matt + */ +public class RecurrenceManager { + + public static List getDetatchedRecurrenceUids(Calendar c) { + ArrayList uids = new ArrayList(); + ComponentList clist = (ComponentList) c.getComponents(Component.VEVENT); + for (int i = 0; i < clist.size(); i++) { + Component occurance = (Component) clist.get(i); + Uid ui = (Uid) occurance.getProperty(Uid.UID); + if (occurance.getProperty(RecurrenceId.RECURRENCE_ID) != null) { + RecurrenceId rid = (RecurrenceId) occurance.getProperty(RecurrenceId.RECURRENCE_ID); + uids.add(rid.getValue()); + } + } + return uids; + } + + public static Calendar obtainStandaloneRecurrence(Calendar c, String recurrenceId) { + ArrayList uids = new ArrayList(); + ComponentList clist = (ComponentList) c.getComponents(Component.VEVENT); + for (int i = 0; i < clist.size(); i++) { + Component occurance = (Component) clist.get(i); + Uid ui = (Uid) occurance.getProperty(Uid.UID); + if (occurance.getProperty(RecurrenceId.RECURRENCE_ID) != null) { + RecurrenceId rid = (RecurrenceId) occurance.getProperty(RecurrenceId.RECURRENCE_ID); + // Correct the UID + ui.setValue(ui.getValue()+"-r"+recurrenceId); + occurance.getProperties().remove(ui); + occurance.getProperties().add(ui); + if (rid.getValue().equals(recurrenceId)) { + Calendar standalone = new Calendar(); + standalone.getComponents().add(occurance); + return standalone; + } + } + } + return null; + } + + public static ExDate getExdateForCalendar(Calendar c, ExDate exd) { + ArrayList detatchedDates = new ArrayList(); + ComponentList clist = (ComponentList) c.getComponents(Component.VEVENT); + for (int i = 0; i < clist.size(); i++) { + Component occurance = (Component) clist.get(i); + if (occurance.getProperty(RecurrenceId.RECURRENCE_ID) != null) { + RecurrenceId rid = (RecurrenceId) occurance.getProperty(RecurrenceId.RECURRENCE_ID); + detatchedDates.add(rid.getDate()); + } + } + for (Date d : detatchedDates) { + exd.getDates().add(d); + } + return exd; + } + + public static Calendar obtainMasterEvent(Calendar c) { + ComponentList clist = (ComponentList) c.getComponents(Component.VEVENT); + for (int i = 0; i < clist.size(); i++) { + Component occurance = (Component) clist.get(i); + if (occurance.getProperty(RecurrenceId.RECURRENCE_ID) == null) { + Calendar cm = new Calendar(); + // Add the changed exdate + ExDate exd = (ExDate) occurance.getProperty(ExDate.EXDATE); + if (exd == null) + exd = new ExDate(); + else + occurance.getProperties().remove(exd); + getExdateForCalendar(c, exd); + occurance.getProperties().add(exd); + cm.getComponents().add(occurance); + return cm; + } + } + return null; + } +} -- 2.11.4.GIT