mirror of
https://github.com/etesync/android
synced 2025-02-02 19:01:06 +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:
parent
9d76d57af8
commit
243483a957
@ -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);
|
||||||
@ -116,18 +105,6 @@ public class Event extends Resource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@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")
|
||||||
public void parseEntity(@NonNull InputStream entity, AssetDownloader downloader) throws IOException, InvalidResourceException {
|
public void parseEntity(@NonNull InputStream entity, AssetDownloader downloader) throws IOException, InvalidResourceException {
|
||||||
@ -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 {
|
||||||
@ -334,6 +305,12 @@ public class Event extends Resource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// time helpers
|
||||||
|
|
||||||
|
public boolean isAllDay() {
|
||||||
|
return !hasTime(dtStart);
|
||||||
|
}
|
||||||
|
|
||||||
public long getDtStartInMillis() {
|
public long getDtStartInMillis() {
|
||||||
return dtStart.getDate().getTime();
|
return dtStart.getDate().getTime();
|
||||||
}
|
}
|
||||||
@ -372,52 +349,4 @@ public class Event extends Resource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
123
app/src/main/java/at/bitfire/davdroid/resource/iCalendar.java
Normal file
123
app/src/main/java/at/bitfire/davdroid/resource/iCalendar.java
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user