001/*
002 * Shredzone Commons - pdb
003 *
004 * Copyright (C) 2009 Richard "Shred" Körber
005 *   http://commons.shredzone.org
006 *
007 * This program is free software: you can redistribute it and/or modify
008 * it under the terms of the GNU Library General Public License as
009 * published by the Free Software Foundation, either version 3 of the
010 * License, or (at your option) any later version.
011 *
012 * This program is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
015 * GNU General Public License for more details.
016 *
017 * You should have received a copy of the GNU Library General Public License
018 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
019 */
020package org.shredzone.commons.pdb.converter;
021
022import java.io.IOException;
023import java.util.Calendar;
024
025import org.shredzone.commons.pdb.PdbDatabase;
026import org.shredzone.commons.pdb.PdbFile;
027import org.shredzone.commons.pdb.appinfo.CategoryAppInfo;
028import org.shredzone.commons.pdb.appinfo.CategoryAppInfo.Category;
029import org.shredzone.commons.pdb.record.ScheduleRecord;
030import org.shredzone.commons.pdb.record.ScheduleRecord.Alarm.Unit;
031import org.shredzone.commons.pdb.record.ScheduleRecord.Repeat.Mode;
032
033/**
034 * An {@link Converter} that reads Calendar records.
035 *
036 * @see <a href="http://search.cpan.org/~bdfoy/p5-Palm-1.011/lib/Datebook.pm">Palm::Datebook</a>
037 */
038public class ScheduleConverter implements Converter<ScheduleRecord, CategoryAppInfo> {
039
040    public static final int FLAG_ALARM = 0x4000;
041    public static final int FLAG_REPEAT = 0x2000;
042    public static final int FLAG_NOTE = 0x1000;
043    public static final int FLAG_EXCEPTIONS = 0x0800;
044    public static final int FLAG_DESCRIPTION = 0x0400;
045    public static final int FLAG_LOCATION = 0x0200;  // only if creator is "PDat"
046
047    @Override
048    public boolean isAcceptable(PdbDatabase<ScheduleRecord, CategoryAppInfo> database) {
049        return "PDat".equals(database.getCreator());
050    }
051
052    @Override
053    public ScheduleRecord convert(PdbFile reader, int record, int size, int attribute,
054            PdbDatabase<ScheduleRecord, CategoryAppInfo> database) throws IOException {
055        ScheduleRecord result = new ScheduleRecord(attribute);
056        if (result.isDelete()) {
057            return null;
058        }
059
060        Category cat = database.getAppInfo().getCategoryByIndex(result.getCategoryIndex());
061        if (cat != null) {
062            result.setCategory(cat.getName());
063        }
064
065        byte startHour = reader.readByte();
066        byte startMinute = reader.readByte();
067        byte endHour = reader.readByte();
068        byte endMinute = reader.readByte();
069        Calendar date = reader.readPackedDate();
070        int flags = reader.readShort();
071
072        if (startHour >= 0 && startMinute >= 0) {
073            result.setStartTime(new ScheduleRecord.ShortTime(startHour, startMinute));
074        }
075
076        if (endHour >= 0 && endMinute >= 0) {
077            result.setEndTime(new ScheduleRecord.ShortTime(endHour, endMinute));
078        }
079
080        result.setSchedule(new ScheduleRecord.ShortDate(date));
081
082        if ((flags & FLAG_ALARM) != 0) {
083            int advance = reader.readByte();
084            int unit = reader.readUnsignedByte();
085
086            ScheduleRecord.Alarm.Unit alarmUnit;
087            switch (unit) {
088                case 0: alarmUnit = Unit.MINUTES; break;
089                case 1: alarmUnit = Unit.HOURS; break;
090                case 2: alarmUnit = Unit.DAYS; break;
091                default: throw new IOException("Unknown alarm unit: " + unit);
092            }
093
094            result.setAlarm(new ScheduleRecord.Alarm(advance, alarmUnit));
095        }
096
097        if ((flags & FLAG_REPEAT) != 0) {
098            int type = reader.readUnsignedByte();
099            reader.readByte();
100
101            ScheduleRecord.Repeat.Mode repeatMode;
102            switch (type) {
103                case 1: repeatMode = Mode.DAILY; break;
104                case 2: repeatMode = Mode.WEEKLY; break;
105                case 3: repeatMode = Mode.MONTHLY_BY_DAY; break;
106                case 4: repeatMode = Mode.MONTHLY; break;
107                case 5: repeatMode = Mode.YEARLY; break;
108                default: throw new IOException("Unknown repeat mode: " + type);
109            }
110
111            ScheduleRecord.ShortDate endDate = null;
112            Calendar ending = reader.readPackedDate();
113            if (ending != null) {
114                endDate = new ScheduleRecord.ShortDate(ending);
115            }
116
117            int frequency = reader.readUnsignedByte();
118            int repeatOn = reader.readUnsignedByte();
119            reader.readUnsignedByte();
120            reader.readByte();
121
122            boolean[] weeklyDays = new boolean[7];
123            if (repeatMode == Mode.WEEKLY) {
124                for (int ix = 0; ix < weeklyDays.length; ix++) {
125                    weeklyDays[ix] = (repeatOn & (1 << ix)) != 0;
126                }
127            }
128
129            int monthlyWeek = 0;
130            int monthlyDay = 0;
131            if (repeatMode == Mode.MONTHLY_BY_DAY) {
132                monthlyWeek = repeatOn / 7;
133                monthlyDay = repeatOn % 7;
134            }
135
136            result.setRepeat(new ScheduleRecord.Repeat(
137                            repeatMode,
138                            frequency,
139                            endDate,
140                            weeklyDays,
141                            monthlyWeek,
142                            monthlyDay
143            ));
144        }
145
146        if ((flags & FLAG_EXCEPTIONS) != 0) {
147            int numExceptions = reader.readUnsignedShort();
148            for (int ix = 0; ix < numExceptions; ix++) {
149                Calendar excDate = reader.readPackedDate();
150                result.getExceptions().add(new ScheduleRecord.ShortDate(excDate));
151            }
152        }
153
154        if ((flags & FLAG_DESCRIPTION) != 0) {
155            result.setDescription(reader.readTerminatedString());
156        }
157
158        if ((flags & FLAG_NOTE) != 0) {
159            result.setNote(reader.readTerminatedString());
160        }
161
162        if ((flags & FLAG_LOCATION) != 0) {
163            result.setLocation(reader.readTerminatedString());
164        }
165
166        return result;
167    }
168
169    @Override
170    public CategoryAppInfo convertAppInfo(PdbFile reader, int size,
171            PdbDatabase<ScheduleRecord, CategoryAppInfo> database) throws IOException {
172        CategoryAppInfo result = new CategoryAppInfo();
173        reader.readCategories(result);
174        return result;
175    }
176
177}