Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ca68e59b authored by Ricki Hirner's avatar Ricki Hirner
Browse files

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
parent 54497ffb
Loading
Loading
Loading
Loading
+8 −79
Original line number Diff line number Diff line
@@ -69,11 +69,9 @@ import lombok.NonNull;
import lombok.Setter;


public class Event extends Resource {
public class Event extends iCalendar {
	private final static String TAG = "davdroid.Event";
	
	private final static TimeZoneRegistry tzRegistry = new DefaultTimeZoneRegistryFactory().createRegistry();

	@Getter @Setter protected RecurrenceId recurrenceId;

	@Getter @Setter protected String summary, location, description;
@@ -97,15 +95,6 @@ public class Event extends Resource {

	@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) {
		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
	@SuppressWarnings("unchecked")
	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
	@SuppressWarnings("unchecked")
	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() {
		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();
	}
}
+11 −15
Original line number Diff line number Diff line
@@ -49,7 +49,7 @@ import at.bitfire.davdroid.syncadapter.DavSyncAdapter;
import lombok.Getter;
import lombok.Setter;

public class Task extends Resource {
public class Task extends iCalendar {
	private final static String TAG = "davdroid.Task";

	@Getter @Setter DateTime createdAt;
@@ -76,13 +76,6 @@ public class Task extends Resource {
		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
	public void parseEntity(InputStream entity, AssetDownloader downloader) throws IOException, InvalidResourceException {
@@ -104,6 +97,10 @@ public class Task extends Resource {

		if (todo.getUid() != null)
			uid = todo.getUid().getValue();
		else {
			Log.w(TAG, "Received VTODO without UID, generating new one");
			generateUID();
		}

		if (todo.getCreated() != null)
			createdAt = todo.getCreated().getDateTime();
@@ -129,20 +126,19 @@ public class Task extends Resource {
			due = todo.getDue();
		if (todo.getDuration() != null)
			duration = todo.getDuration();
		if (todo.getStartDate() != null)
		if (todo.getStartDate() != null) {
			dtStart = todo.getStartDate();
		if (todo.getDateCompleted() != null)
			validateTimeZone(dtStart);
		}
		if (todo.getDateCompleted() != null) {
			completedAt = todo.getDateCompleted();
			validateTimeZone(completedAt);
		}
		if (todo.getPercentComplete() != null)
			percentComplete = todo.getPercentComplete().getPercentage();
	}


	@Override
	public String getMimeType() {
		return "text/calendar";
	}

	@Override
	public ByteArrayOutputStream toEntity() throws IOException {
		final net.fortuna.ical4j.model.Calendar ical = new net.fortuna.ical4j.model.Calendar();
+123 −0
Original line number Diff line number Diff line
/*
 * 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();
	}

}