1
0
mirror of https://github.com/etesync/android synced 2025-02-02 10:51:10 +00:00

Improved iCal generation

* move shared code to new iCalendar class
* generate UIDs and file names with "_" instead of "@" to reduce encoding problems (closes #585)
* tasks: validate "start date" and "completed at" time zones
This commit is contained in:
Ricki Hirner 2015-07-28 15:29:54 +02:00
parent 9d76d57af8
commit 243483a957
3 changed files with 142 additions and 94 deletions

View File

@ -69,11 +69,9 @@ import lombok.NonNull;
import lombok.Setter; import lombok.Setter;
public class Event extends Resource { public class Event extends iCalendar {
private final static String TAG = "davdroid.Event"; private final static String TAG = "davdroid.Event";
private final static TimeZoneRegistry tzRegistry = new DefaultTimeZoneRegistryFactory().createRegistry();
@Getter @Setter protected RecurrenceId recurrenceId; @Getter @Setter protected RecurrenceId recurrenceId;
@Getter @Setter protected String summary, location, description; @Getter @Setter protected String summary, location, description;
@ -97,15 +95,6 @@ public class Event extends Resource {
@Getter protected List<VAlarm> alarms = new LinkedList<>(); @Getter protected List<VAlarm> alarms = new LinkedList<>();
static {
CompatibilityHints.setHintEnabled(CompatibilityHints.KEY_RELAXED_UNFOLDING, true);
CompatibilityHints.setHintEnabled(CompatibilityHints.KEY_RELAXED_PARSING, true);
CompatibilityHints.setHintEnabled(CompatibilityHints.KEY_OUTLOOK_COMPATIBILITY, true);
// disable automatic time-zone updates (causes unnecessary network traffic for most people)
System.setProperty("net.fortuna.ical4j.timezone.update.enabled", "false");
}
public Event(String name, String ETag) { public Event(String name, String ETag) {
super(name, ETag); super(name, ETag);
@ -115,18 +104,6 @@ public class Event extends Resource {
super(localID, name, ETag); super(localID, name, ETag);
} }
@Override
public void initialize() {
generateUID();
name = uid.replace("@", "_") + ".ics";
}
protected void generateUID() {
UidGenerator generator = new UidGenerator(new SimpleHostInfo(DavSyncAdapter.getAndroidID()), String.valueOf(android.os.Process.myPid()));
uid = generator.generateUid().getValue();
}
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -228,12 +205,6 @@ public class Event extends Resource {
} }
@Override
public String getMimeType() {
return "text/calendar";
}
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public ByteArrayOutputStream toEntity() throws IOException { public ByteArrayOutputStream toEntity() throws IOException {
@ -333,7 +304,13 @@ public class Event extends Resource {
return event; return event;
} }
// time helpers
public boolean isAllDay() {
return !hasTime(dtStart);
}
public long getDtStartInMillis() { public long getDtStartInMillis() {
return dtStart.getDate().getTime(); return dtStart.getDate().getTime();
} }
@ -370,54 +347,6 @@ public class Event extends Resource {
dtEnd = new DtEnd(end); dtEnd = new DtEnd(end);
} }
} }
// helpers
public boolean isAllDay() {
return !hasTime(dtStart);
}
protected static boolean hasTime(DateProperty date) {
return date.getDate() instanceof DateTime;
}
protected static String getTzId(DateProperty date) {
if (date.isUtc() || !hasTime(date))
return Time.TIMEZONE_UTC;
else if (date.getTimeZone() != null)
return date.getTimeZone().getID();
else if (date.getParameter(Value.TZID) != null)
return date.getParameter(Value.TZID).getValue();
// fallback
return Time.TIMEZONE_UTC;
}
/* guess matching Android timezone ID */
protected static void validateTimeZone(DateProperty date) {
if (date.isUtc() || !hasTime(date))
return;
String tzID = getTzId(date);
if (tzID == null)
return;
String localTZ = DateUtils.findAndroidTimezoneID(tzID);
date.setTimeZone(tzRegistry.getTimeZone(localTZ));
}
public static String TimezoneDefToTzId(String timezoneDef) throws IllegalArgumentException {
try {
if (timezoneDef != null) {
CalendarBuilder builder = new CalendarBuilder();
net.fortuna.ical4j.model.Calendar cal = builder.build(new StringReader(timezoneDef));
VTimeZone timezone = (VTimeZone)cal.getComponent(VTimeZone.VTIMEZONE);
return timezone.getTimeZoneId().getValue();
}
} catch (Exception ex) {
Log.w(TAG, "Can't understand time zone definition, ignoring", ex);
}
throw new IllegalArgumentException();
}
} }

View File

@ -49,7 +49,7 @@ import at.bitfire.davdroid.syncadapter.DavSyncAdapter;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
public class Task extends Resource { public class Task extends iCalendar {
private final static String TAG = "davdroid.Task"; private final static String TAG = "davdroid.Task";
@Getter @Setter DateTime createdAt; @Getter @Setter DateTime createdAt;
@ -76,13 +76,6 @@ public class Task extends Resource {
super(localId, name, ETag); super(localId, name, ETag);
} }
@Override
public void initialize() {
UidGenerator generator = new UidGenerator(new SimpleHostInfo(DavSyncAdapter.getAndroidID()), String.valueOf(android.os.Process.myPid()));
uid = generator.generateUid().getValue();
name = uid + ".ics";
}
@Override @Override
public void parseEntity(InputStream entity, AssetDownloader downloader) throws IOException, InvalidResourceException { public void parseEntity(InputStream entity, AssetDownloader downloader) throws IOException, InvalidResourceException {
@ -104,6 +97,10 @@ public class Task extends Resource {
if (todo.getUid() != null) if (todo.getUid() != null)
uid = todo.getUid().getValue(); uid = todo.getUid().getValue();
else {
Log.w(TAG, "Received VTODO without UID, generating new one");
generateUID();
}
if (todo.getCreated() != null) if (todo.getCreated() != null)
createdAt = todo.getCreated().getDateTime(); createdAt = todo.getCreated().getDateTime();
@ -129,20 +126,19 @@ public class Task extends Resource {
due = todo.getDue(); due = todo.getDue();
if (todo.getDuration() != null) if (todo.getDuration() != null)
duration = todo.getDuration(); duration = todo.getDuration();
if (todo.getStartDate() != null) if (todo.getStartDate() != null) {
dtStart = todo.getStartDate(); dtStart = todo.getStartDate();
if (todo.getDateCompleted() != null) validateTimeZone(dtStart);
}
if (todo.getDateCompleted() != null) {
completedAt = todo.getDateCompleted(); completedAt = todo.getDateCompleted();
validateTimeZone(completedAt);
}
if (todo.getPercentComplete() != null) if (todo.getPercentComplete() != null)
percentComplete = todo.getPercentComplete().getPercentage(); percentComplete = todo.getPercentComplete().getPercentage();
} }
@Override
public String getMimeType() {
return "text/calendar";
}
@Override @Override
public ByteArrayOutputStream toEntity() throws IOException { public ByteArrayOutputStream toEntity() throws IOException {
final net.fortuna.ical4j.model.Calendar ical = new net.fortuna.ical4j.model.Calendar(); final net.fortuna.ical4j.model.Calendar ical = new net.fortuna.ical4j.model.Calendar();

View File

@ -0,0 +1,123 @@
/*
* Copyright © 2013 2015 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.davdroid.resource;
import android.text.format.Time;
import android.util.Log;
import net.fortuna.ical4j.data.CalendarBuilder;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.DefaultTimeZoneRegistryFactory;
import net.fortuna.ical4j.model.TimeZoneRegistry;
import net.fortuna.ical4j.model.component.VTimeZone;
import net.fortuna.ical4j.model.parameter.Value;
import net.fortuna.ical4j.model.property.DateProperty;
import net.fortuna.ical4j.model.property.DtStart;
import net.fortuna.ical4j.util.CompatibilityHints;
import net.fortuna.ical4j.util.SimpleHostInfo;
import net.fortuna.ical4j.util.UidGenerator;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import at.bitfire.davdroid.DateUtils;
import at.bitfire.davdroid.syncadapter.DavSyncAdapter;
import lombok.Getter;
public abstract class iCalendar extends Resource {
static private final String TAG = "DAVdroid.iCal";
// static ical4j initialization
static {
CompatibilityHints.setHintEnabled(CompatibilityHints.KEY_RELAXED_UNFOLDING, true);
CompatibilityHints.setHintEnabled(CompatibilityHints.KEY_RELAXED_PARSING, true);
CompatibilityHints.setHintEnabled(CompatibilityHints.KEY_OUTLOOK_COMPATIBILITY, true);
// disable automatic time-zone updates (causes unwanted network traffic)
System.setProperty("net.fortuna.ical4j.timezone.update.enabled", "false");
}
static protected final TimeZoneRegistry tzRegistry = new DefaultTimeZoneRegistryFactory().createRegistry();
public iCalendar(long localID, String name, String ETag) {
super(localID, name, ETag);
}
public iCalendar(String name, String ETag) {
super(name, ETag);
}
@Override
public void initialize() {
generateUID();
name = uid + ".ics";
}
protected void generateUID() {
UidGenerator generator = new UidGenerator(new SimpleHostInfo(DavSyncAdapter.getAndroidID()), String.valueOf(android.os.Process.myPid()));
uid = generator.generateUid().getValue().replace("@", "_");
}
@Override
public String getMimeType() {
return "text/calendar";
}
// time zone helpers
protected static boolean hasTime(DateProperty date) {
return date.getDate() instanceof DateTime;
}
protected static String getTzId(DateProperty date) {
if (date.isUtc() || !hasTime(date))
return Time.TIMEZONE_UTC;
else if (date.getTimeZone() != null)
return date.getTimeZone().getID();
else if (date.getParameter(Value.TZID) != null)
return date.getParameter(Value.TZID).getValue();
// fallback
return Time.TIMEZONE_UTC;
}
/* guess matching Android timezone ID */
protected static void validateTimeZone(DateProperty date) {
if (date.isUtc() || !hasTime(date))
return;
String tzID = getTzId(date);
if (tzID == null)
return;
String localTZ = DateUtils.findAndroidTimezoneID(tzID);
date.setTimeZone(tzRegistry.getTimeZone(localTZ));
}
public static String TimezoneDefToTzId(String timezoneDef) throws IllegalArgumentException {
try {
if (timezoneDef != null) {
CalendarBuilder builder = new CalendarBuilder();
net.fortuna.ical4j.model.Calendar cal = builder.build(new StringReader(timezoneDef));
VTimeZone timezone = (VTimeZone)cal.getComponent(VTimeZone.VTIMEZONE);
return timezone.getTimeZoneId().getValue();
}
} catch (Exception ex) {
Log.w(TAG, "Can't understand time zone definition, ignoring", ex);
}
throw new IllegalArgumentException();
}
}