Tone down some of the logging.
[acal.git] / src / com / morphoss / acal / davacal / VCalendar.java
bloba65c51e8530497cec944d63ba662801a5e7b0fc8
1 /*
2 * Copyright (C) 2011 Morphoss Ltd
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 package com.morphoss.acal.davacal;
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.TimeZone;
26 import java.util.regex.Matcher;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import android.util.Log;
32 import com.morphoss.acal.Constants;
33 import com.morphoss.acal.acaltime.AcalDateRange;
34 import com.morphoss.acal.acaltime.AcalDateTime;
35 import com.morphoss.acal.acaltime.AcalRepeatRule;
36 import com.morphoss.acal.acaltime.AcalRepeatRuleParser;
38 public class VCalendar extends VComponent {
39 public static final String TAG = "aCal VCalendar";
40 private AcalDateRange dateRange = null;
41 private AcalRepeatRule repeatRule = null;
42 private Boolean masterHasOverrides = null;
43 private Boolean hasAlarms = null;
44 private Boolean hasRepeatRule = null;
45 private Long earliestStart;
46 private Long latestEnd;
47 private boolean isPending = false;
50 public VCalendar(ComponentParts splitter, Integer resourceId, Long earliestStart, Long latestEnd, AcalCollection collectionObject,VComponent parent) {
51 super(splitter, resourceId, collectionObject,parent);
52 this.earliestStart = earliestStart;
53 this.latestEnd = latestEnd;
54 if ( earliestStart != null ) {
55 this.dateRange = new AcalDateRange(AcalDateTime.fromMillis(earliestStart),
56 (latestEnd == null ? null : AcalDateTime.fromMillis(latestEnd)));
61 protected VCalendar(AcalCollection collection) {
62 super( VComponent.VCALENDAR, collection, null );
63 try { setPersistentOn(); } catch (YouMustSurroundThisMethodInTryCatchOrIllEatYouException e) { }
64 addProperty(new AcalProperty("CALSCALE","GREGORIAN"));
65 addProperty(new AcalProperty("PRODID","-//morphoss.com//aCal 1.0//EN"));
66 addProperty(new AcalProperty("VERSION","2.0"));
70 public static VCalendar getGenericCalendar( AcalCollection collection, AcalEvent newEventData) {
71 VCalendar vcal = new VCalendar(collection);
72 new VEvent(vcal);
73 return vcal;
76 public void setPending(boolean isPending) {
77 this.isPending = isPending;
79 public boolean isPending() {
80 return this.isPending;
83 public VCalendar clone() {
84 return new VCalendar(this.content, this.resourceId, this.earliestStart, this.latestEnd, this.collectionData, this.parent);
87 public String applyEventAction(AcalEvent action) {
88 try {
89 this.setEditable();
91 VEvent vEvent = (VEvent) this.getMasterChild();
93 // first, strip any existing properties which we always modify
94 vEvent.removeProperties( new PropertyName[] {PropertyName.DTSTAMP, PropertyName.LAST_MODIFIED } );
96 // change DTStamp
97 AcalDateTime lastModified = new AcalDateTime();
98 lastModified.setTimeZone(TimeZone.getDefault().getID());
99 lastModified.shiftTimeZone("UTC");
101 vEvent.addProperty(AcalProperty.fromString(lastModified.toPropertyString(PropertyName.DTSTAMP)));
102 vEvent.addProperty(AcalProperty.fromString(lastModified.toPropertyString(PropertyName.LAST_MODIFIED)));
104 if ( action.getAction() == AcalEvent.ACTION_DELETE_SINGLE) {
105 AcalProperty exDate = vEvent.getProperty("EXDATE");
106 if ( exDate == null || exDate.getValue().equals("") )
107 exDate = AcalProperty.fromString(action.getStart().toPropertyString(PropertyName.EXDATE));
108 else {
109 vEvent.removeProperties( new PropertyName[] {PropertyName.EXDATE} );
110 exDate = AcalProperty.fromString(exDate.toRfcString() + "," + action.getStart().fmtIcal() );
112 vEvent.addProperty(exDate);
114 else if (action.getAction() == AcalEvent.ACTION_DELETE_ALL_FUTURE) {
115 AcalRepeatRuleParser parsedRule = AcalRepeatRuleParser.parseRepeatRule(action.getRepetition());
116 AcalDateTime until = action.getStart().clone();
117 until.addSeconds(-1);
118 parsedRule.setUntil(until);
119 String rrule = parsedRule.toString();
120 action.setRepetition(rrule);
121 vEvent.removeProperties( new PropertyName[] {PropertyName.RRULE} );
122 vEvent.addProperty(new AcalProperty("RRULE",rrule));
124 else if (action.isModifyAction()) {
125 this.applyModify(vEvent,action);
126 this.updateTimeZones(vEvent);
129 String ret = this.getCurrentBlob();
130 return ret;
132 catch (Exception e) {
133 Log.w(TAG,Log.getStackTraceString(e));
134 return "";
139 private void applyModify(Masterable mast, AcalEvent action) {
140 //there are 3 possible modify actions:
141 if (action.getAction() == AcalEvent.ACTION_MODIFY_SINGLE) {
142 // Only modify the single instance
144 else if (action.getAction() == AcalEvent.ACTION_MODIFY_ALL_FUTURE) {
145 // Modify this instance, and all future instances.
148 else if (action.getAction() == AcalEvent.ACTION_MODIFY_ALL) {
149 // Modify all instances
151 // First, strip any existing properties which we modify
152 mast.removeProperties( new PropertyName[] {PropertyName.DTSTART, PropertyName.DTEND, PropertyName.DURATION,
153 PropertyName.SUMMARY, PropertyName.LOCATION, PropertyName.DESCRIPTION, PropertyName.RRULE } );
155 AcalDateTime dtStart = action.getStart();
156 mast.addProperty( dtStart.asProperty(PropertyName.DTSTART));
158 AcalDateTime dtEnd = action.getEnd();
159 if ( (dtEnd.getTimeZoneId() == null && dtStart.getTimeZoneId() == null) ||
160 (dtEnd.getTimeZoneId() != null && dtEnd.getTimeZoneId().equals(dtStart.getTimeZoneId())) )
161 mast.addProperty(action.getDuration().asProperty(PropertyName.DURATION) );
162 else
163 mast.addProperty( dtEnd.asProperty( PropertyName.DTEND ) );
165 mast.addProperty(new AcalProperty(PropertyName.SUMMARY, action.getSummary()));
167 String location = action.getLocation();
168 if ( !location.equals("") )
169 mast.addProperty(new AcalProperty(PropertyName.LOCATION,location));
171 String description = action.getDescription();
172 if ( !description.equals("") )
173 mast.addProperty(new AcalProperty(PropertyName.DESCRIPTION,description));
175 String rrule = action.getRepetition();
176 if ( rrule != null && !rrule.equals(""))
177 mast.addProperty(new AcalProperty(PropertyName.RRULE,rrule));
179 mast.updateAlarmComponents( action.getAlarms() );
183 private void updateTimeZones(VEvent vEvent) {
184 HashSet<String> tzIdSet = new HashSet<String>();
185 for( PropertyName pn : PropertyName.localisableDateProperties() ) {
186 AcalProperty p = vEvent.getProperty(pn);
187 if ( p != null ) {
188 String tzId = p.getParam("TZID");
189 if ( tzId != null ) {
190 tzIdSet.add(p.getParam("TZID"));
191 if ( Constants.LOG_DEBUG )
192 Log.println(Constants.LOGD,TAG,"Found reference to timezone '"+tzId+"' in event.");
197 List<VComponent> removeChildren = new ArrayList<VComponent>();
198 for (VComponent child : getChildren() ) {
199 if ( child.name.equals(VComponent.VTIMEZONE) ) {
200 String tzId = null;
201 try {
202 VTimezone vtz = (VTimezone) child;
203 tzId = vtz.getTZID();
205 catch( Exception e ) {};
206 if ( tzIdSet.contains(tzId) ) {
207 if ( Constants.LOG_DEBUG )
208 Log.println(Constants.LOGD,TAG,"Found child vtimezone for '"+tzId+"' in event.");
209 tzIdSet.remove(tzId);
211 else {
212 if ( Constants.LOG_DEBUG )
213 Log.println(Constants.LOGD,TAG,"Removing vtimezone for '"+tzId+"' from event.");
214 removeChildren.add(child);
217 else {
218 if ( Constants.LOG_DEBUG )
219 Log.println(Constants.LOGD,TAG,"Found "+child.name+" component in event.");
222 // Have to avoid the concurrent modification
223 for(VComponent child : removeChildren ) {
224 this.removeChild(child);
227 for ( String tzId : tzIdSet ) {
228 VTimezone vtz;
229 try {
230 String tzBlob = VTimezone.getZoneDefinition(tzId);
231 if ( Constants.LOG_DEBUG ) {
232 Log.println(Constants.LOGD,TAG,"New timezone for '"+tzId+"'");
233 Log.println(Constants.LOGD,TAG,tzBlob);
235 vtz = (VTimezone) VComponent.createComponentFromBlob(tzBlob, null, collectionData);
236 vtz.setEditable();
237 this.addChild(vtz);
239 catch ( UnrecognizedTimeZoneException e ) {
240 Log.i(TAG,"Unable to build a timezone for '"+tzId+"'");
247 public void checkRepeatRule() {
248 try {
249 if (repeatRule == null) repeatRule = AcalRepeatRule.fromVCalendar(this);
251 catch ( Exception e ) {
252 Log.e(TAG,"Exception getting repeat rule from VCalendar", e);
254 hasRepeatRule = ( repeatRule != null );
257 public boolean appendAlarmInstancesBetween(List<AcalAlarm> alarmList, AcalDateRange rangeRequested) {
258 if ( hasRepeatRule == null && repeatRule == null ) checkRepeatRule();
259 if ( !hasRepeatRule ) return false;
260 this.repeatRule.appendAlarmInstancesBetween(alarmList, rangeRequested);
261 return true;
264 public boolean appendEventInstancesBetween(List<AcalEvent> eventList, AcalDateRange rangeRequested, boolean isPending) {
265 if (isPending) {
266 this.isPending = true;
267 this.repeatRule = null;
269 try {
270 if (dateRange != null) {
271 AcalDateRange intersection = rangeRequested.getIntersection(this.dateRange);
272 if (intersection != null) {
273 if (hasRepeatRule == null && repeatRule == null) checkRepeatRule();
274 if (hasRepeatRule) {
275 // Log.d(TAG,"Processing event: Summary="+new UnModifiableAcalEvent(thisEvent,new AcalCalendar(), new AcalCalendar()).summary);
276 this.repeatRule.appendEventsInstancesBetween(eventList, intersection);
277 return true;
281 else {
282 checkRepeatRule();
283 if (repeatRule != null) {
284 this.repeatRule.appendEventsInstancesBetween(eventList, rangeRequested);
285 return true;
288 // Log.d(TAG,"Skipped event: Summary="+new UnModifiableAcalEvent(thisEvent,new AcalCalendar(), new AcalCalendar()).summary);
290 catch(Exception e) {
291 if (Constants.LOG_DEBUG)Log.d(TAG,"Exception in RepeatRule handling");
292 if (Constants.LOG_DEBUG)Log.d(TAG,Log.getStackTraceString(e));
294 return false;
297 public Masterable getMasterChild() {
298 if (childrenSet) {
299 for (VComponent vc : this.getChildren()) {
300 if ( vc instanceof VEvent) return (VEvent)vc;
301 if ( vc instanceof VTodo ) return (VTodo) vc;
304 for( PartInfo childInfo : content.partInfo ) {
305 if ( childInfo.type.equals(VEVENT) ) {
306 return new VEvent(new ComponentParts(childInfo.getComponent(content.componentString)),
307 resourceId, collectionData,this);
309 else if ( childInfo.type.equals(VTODO) ) {
310 return new VTodo(new ComponentParts(childInfo.getComponent(content.componentString)),
311 resourceId, collectionData,this);
314 return null;
317 public AcalDateTime getRangeEnd() {
318 if ( dateRange == null ) return null;
319 return dateRange.end;
322 public boolean masterHasOverrides() {
323 if ( masterHasOverrides == null ) {
324 int countMasterables = 0;
325 for( PartInfo childInfo : content.partInfo ) {
326 if ( childInfo.type.equals(VEVENT) || childInfo.type.equals(VTODO) ) {
327 countMasterables++;
328 if ( countMasterables > 1 ) break;
331 if ( masterHasOverrides == null ) masterHasOverrides = (countMasterables > 1);
333 return masterHasOverrides;
337 public Masterable getChildFromRecurrenceId(RecurrenceId recurrenceProperty) {
338 if ( masterHasOverrides() ) return this.getMasterChild();
339 try {
340 this.setPersistentOn();
341 List<Masterable> matchingChildren = new ArrayList<Masterable>();
342 for (VComponent vc: this.getChildren()) {
343 if (vc.containsPropertyKey(recurrenceProperty.getName()) && vc instanceof Masterable)
344 matchingChildren.add((Masterable) vc);
346 if (matchingChildren.isEmpty()) {
347 // Won't happen since we test for this in masterHasOverrides()
348 return this.getMasterChild();
350 Collections.sort(matchingChildren, RecurrenceId.getVComponentComparatorByRecurrenceId());
351 for (int i = matchingChildren.size()-1; i>= 0; i--) {
352 RecurrenceId cur = (RecurrenceId) matchingChildren.get(i).getProperty("RECURRENCE-ID");
353 if (cur.notAfter(recurrenceProperty)) {
354 this.setPersistentOff();
355 return matchingChildren.get(i);
358 } catch (YouMustSurroundThisMethodInTryCatchOrIllEatYouException e) {
359 Log.w(TAG,Log.getStackTraceString(e));
360 } finally {
361 this.setPersistentOff();
364 return this.getMasterChild();
368 public String getOlsonName( String TzID ) {
369 Matcher m = Constants.tzOlsonExtractor.matcher(TzID);
370 if ( m.matches() ) {
371 return m.group(1);
374 if (childrenSet) {
375 for (VComponent vc : this.getChildren()) {
376 if ( vc instanceof VTimezone ) {
377 if ( vc.getProperty("TZID").getValue() == TzID ) {
378 AcalProperty idProperty = vc.getProperty("X-MICROSOFT-CDO-TZID");
379 if ( idProperty != null && idProperty.getValue() != null ) {
380 switch( Integer.parseInt(idProperty.getValue()) ) {
381 case 0: return("UTC");
382 case 1: return("Europe/London");
383 case 2: return("Europe/Lisbon");
384 case 3: return("Europe/Paris");
385 case 4: return("Europe/Berlin");
386 case 5: return("Europe/Bucharest");
387 case 6: return("Europe/Prague");
388 case 7: return("Europe/Athens");
389 case 8: return("America/Brasilia");
390 case 9: return("America/Halifax");
391 case 10: return("America/New_York");
392 case 11: return("America/Chicago");
393 case 12: return("America/Denver");
394 case 13: return("America/Los_Angeles");
395 case 14: return("America/Anchorage");
396 case 15: return("Pacific/Honolulu");
397 case 16: return("Pacific/Apia");
398 case 17: return("Pacific/Auckland");
399 case 18: return("Australia/Brisbane");
400 case 19: return("Australia/Adelaide");
401 case 20: return("Asia/Tokyo");
402 case 21: return("Asia/Singapore");
403 case 22: return("Asia/Bangkok");
404 case 23: return("Asia/Kolkata");
405 case 24: return("Asia/Muscat");
406 case 25: return("Asia/Tehran");
407 case 26: return("Asia/Baghdad");
408 case 27: return("Asia/Jerusalem");
409 case 28: return("America/St_Johns");
410 case 29: return("Atlantic/Azores");
411 case 30: return("America/Noronha");
412 case 31: return("Africa/Casablanca");
413 case 32: return("America/Argentina/Buenos_Aires");
414 case 33: return("America/La_Paz");
415 case 34: return("America/Indiana/Indianapolis");
416 case 35: return("America/Bogota");
417 case 36: return("America/Regina");
418 case 37: return("America/Tegucigalpa");
419 case 38: return("America/Phoenix");
420 case 39: return("Pacific/Kwajalein");
421 case 40: return("Pacific/Fiji");
422 case 41: return("Asia/Magadan");
423 case 42: return("Australia/Hobart");
424 case 43: return("Pacific/Guam");
425 case 44: return("Australia/Darwin");
426 case 45: return("Asia/Shanghai");
427 case 46: return("Asia/Novosibirsk");
428 case 47: return("Asia/Karachi");
429 case 48: return("Asia/Kabul");
430 case 49: return("Africa/Cairo");
431 case 50: return("Africa/Harare");
432 case 51: return("Europe/Moscow");
433 case 53: return("Atlantic/Cape_Verde");
434 case 54: return("Asia/Yerevan");
435 case 55: return("America/Panama");
436 case 56: return("Africa/Nairobi");
437 case 58: return("Asia/Yekaterinburg");
438 case 59: return("Europe/Helsinki");
439 case 60: return("America/Godthab");
440 case 61: return("Asia/Rangoon");
441 case 62: return("Asia/Kathmandu");
442 case 63: return("Asia/Irkutsk");
443 case 64: return("Asia/Krasnoyarsk");
444 case 65: return("America/Santiago");
445 case 66: return("Asia/Colombo");
446 case 67: return("Pacific/Tongatapu");
447 case 68: return("Asia/Vladivostok");
448 case 69: return("Africa/Ndjamena");
449 case 70: return("Asia/Yakutsk");
450 case 71: return("Asia/Dhaka");
451 case 72: return("Asia/Seoul");
452 case 73: return("Australia/Perth");
453 case 74: return("Asia/Riyadh");
454 case 75: return("Asia/Taipei");
455 case 76: return("Australia/Sydney");
457 case 57: // null
458 case 52: // null
459 default: // null
467 * @todo: We should
469 return null; // We failed :-(
473 public boolean hasAlarm() {
474 if ( this.hasAlarms != null ) return this.hasAlarms;
475 for( PartInfo childInfo : content.partInfo ) {
476 if ( childInfo.type.equals(VEVENT) ) {
477 VEvent vc = new VEvent(new ComponentParts(childInfo.getComponent(content.componentString)),
478 resourceId, collectionData,this);
479 for( PartInfo childChildInfo : vc.content.partInfo ) {
480 if ( childChildInfo.type.equals(VALARM)) {
481 this.hasAlarms = true;
482 return true;
486 else if ( childInfo.type.equals(VTODO) ) {
487 VTodo vc = new VTodo(new ComponentParts(childInfo.getComponent(content.componentString)),
488 resourceId, collectionData,this);
489 for( PartInfo childChildInfo : vc.content.partInfo ) {
490 if ( childChildInfo.type.equals(VALARM)) {
491 this.hasAlarms = true;
492 return true;
496 else if ( childInfo.type.equals(VALARM)) {
497 this.hasAlarms = true;
498 return true;
501 this.hasAlarms = false;
502 return false;
506 public VCalendar(Parcel in) {
507 super(in);
510 public static final Parcelable.Creator<VCalendar> CREATOR = new Parcelable.Creator<VCalendar>() {
511 public VCalendar createFromParcel(Parcel in) {
512 return new VCalendar(in);
515 public VCalendar[] newArray(int size) {
516 return new VCalendar[size];