calendar/js/app/service/objectConverter.js

620 lines
18 KiB
JavaScript

/**
* ownCloud - Calendar App
*
* @author Raghu Nayyar
* @author Georg Ehrke
* @copyright 2016 Raghu Nayyar <beingminimal@gmail.com>
* @copyright 2016 Georg Ehrke <oc.list@georgehrke.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
app.factory('objectConverter', function () {
'use strict';
/**
* structure of simple data
*/
var defaults = {
'summary': null,
'x-oc-calid': null,
'location': null,
'created': null,
'last-modified': null,
'organizer': null,
'x-oc-cruds': null,
'class': null,
'description': null,
'url': null,
'status': null,
'resources': null,
'alarm': null,
'attendee': null,
'categories': null,
'dtstart': null,
'dtend': null,
'repeating': null,
'rdate': null,
'rrule': null,
'exdate': null
};
var attendeeParameters = [
'role',
'rvsp',
'partstat',
'cutype',
'cn',
'delegated-from',
'delegated-to'
];
/**
* parsers of supported properties
*/
var simpleParser = {
date: function(data, vevent, key, parameters) {
parameters = (parameters || []).concat(['tzid']);
simpleParser._parseSingle(data, vevent, key, parameters, function(p) {
return (p.type === 'duration') ?
p.getFirstValue().toSeconds() :
p.getFirstValue().toJSDate();
});
},
dates: function(data, vevent, key, parameters) {
parameters = (parameters || []).concat(['tzid']);
simpleParser._parseMultiple(data, vevent, key, parameters, function(p) {
var values = p.getValues(),
usableValues = [];
for (var vKey in values) {
if (!values.hasOwnProperty(vKey)) {
continue;
}
usableValues.push(
(p.type === 'duration') ?
values[vKey].toSeconds() :
values[vKey].toJSDate()
);
}
return usableValues;
});
},
string: function(data, vevent, key, parameters) {
simpleParser._parseSingle(data, vevent, key, parameters, function(p) {
return p.isMultiValue ? p.getValues() : p.getFirstValue();
});
},
strings: function(data, vevent, key, parameters) {
simpleParser._parseMultiple(data, vevent, key, parameters, function(p) {
return p.isMultiValue ? p.getValues() : p.getFirstValue();
});
},
_parseSingle: function(data, vevent, key, parameters, valueParser) {
var prop = vevent.getFirstProperty(key);
if (!prop) {
return;
}
data[key] = {
parameters: simpleParser._parseParameters(prop, parameters),
type: prop.type
};
if (prop.isMultiValue) {
angular.extend(data[key], {
values: valueParser(prop)
});
} else {
angular.extend(data[key], {
value: valueParser(prop)
});
}
},
_parseMultiple: function(data, vevent, key, parameters, valueParser) {
data[key] = data[key] || [];
var properties = vevent.getAllProperties(key),
group = 0;
for (var pKey in properties) {
if (!properties.hasOwnProperty(pKey)) {
continue;
}
var values = valueParser(properties[pKey]);
var currentElement = {
group: group,
parameters: simpleParser._parseParameters(properties[pKey], parameters),
type: properties[pKey].type,
values: values
};
if (properties[pKey].isMultiValue) {
angular.extend(currentElement, {
values: valueParser(properties[pKey])
});
} else {
angular.extend(currentElement, {
value: valueParser(properties[pKey])
});
}
data[key].push(currentElement);
properties[pKey].setParameter('x-oc-group-id', group.toString());
group++;
}
},
_parseParameters: function(prop, para) {
var parameters = {};
if (!para) {
return parameters;
}
for (var i=0,l=para.length; i < l; i++) {
parameters[para[i]] = prop.getParameter(para[i]);
}
return parameters;
}
};
var simpleReader = {
date: function(vevent, oldSimpleData, newSimpleData, key, parameters) {
parameters = (parameters || []).concat(['tzid']);
simpleReader._readSingle(vevent, oldSimpleData, newSimpleData, key, parameters, function(v, isMultiValue) {
if (v.type === 'duration') {
return ICAL.Duration.fromSeconds(v.value);
} else {
return ICAL.Time.fromJSDate(v.value);
}
});
},
dates: function(vevent, oldSimpleData, newSimpleData, key, parameters) {
parameters = (parameters || []).concat(['tzid']);
simpleReader._readMultiple(vevent, oldSimpleData, newSimpleData, key, parameters, function(v, isMultiValue) {
var values = [];
for (var i=0, length=v.values.length; i < length; i++) {
if (v.type === 'duration') {
values.push(ICAL.Duration.fromSeconds(v.values[i]));
} else {
values.push(ICAL.Time.fromJSDate(v.values[i]));
}
}
return values;
});
},
string: function(vevent, oldSimpleData, newSimpleData, key, parameters) {
simpleReader._readSingle(vevent, oldSimpleData, newSimpleData, key, parameters, function(v, isMultiValue) {
return isMultiValue ? v.values : v.value;
});
},
strings: function(vevent, oldSimpleData, newSimpleData, key, parameters) {
simpleReader._readMultiple(vevent, oldSimpleData, newSimpleData, key, parameters, function(v, isMultiValue) {
return isMultiValue ? v.values : v.value;
});
},
_readSingle: function(vevent, oldSimpleData, newSimpleData, key, parameters, valueReader) {
if (!newSimpleData[key]) {
return;
}
if (!newSimpleData[key].hasOwnProperty('value') && !newSimpleData[key].hasOwnProperty('values')) {
return;
}
var isMultiValue = newSimpleData[key].hasOwnProperty('values');
var prop = vevent.updatePropertyWithValue(key, valueReader(newSimpleData[key], isMultiValue));
simpleReader._readParameters(prop, newSimpleData[key], parameters);
},
_readMultiple: function(vevent, oldSimpleData, newSimpleData, key, parameters, valueReader) {
var oldGroups=[], properties=null, pKey=null, groupId;
oldSimpleData[key] = oldSimpleData[key] || [];
for (var i=0, oldLength=oldSimpleData[key].length; i < oldLength; i++) {
oldGroups.push(oldSimpleData[key][i].group);
}
newSimpleData[key] = newSimpleData[key] || [];
for (var j=0, newLength=newSimpleData[key].length; j < newLength; j++) {
var isMultiValue = newSimpleData[key][j].hasOwnProperty('values');
var value = valueReader(newSimpleData[key][j], isMultiValue);
if (oldGroups.indexOf(newSimpleData[key][j].group) === -1) {
var property = new ICAL.Property(key);
simpleReader._setProperty(property, value, isMultiValue);
simpleReader._readParameters(property, newSimpleData[key][j], parameters);
vevent.addProperty(property);
} else {
oldGroups.splice(oldGroups.indexOf(newSimpleData[key][j].group), 1);
properties = vevent.getAllProperties(key);
for (pKey in properties) {
if (!properties.hasOwnProperty(pKey)) {
continue;
}
groupId = properties[pKey].getParameter('x-oc-group-id');
if (groupId === null) {
continue;
}
if (groupId === newSimpleData[key][j].group) {
simpleReader._setProperty(properties[pKey], value, isMultiValue);
simpleReader._readParameters(properties[pKey], newSimpleData[key][j], parameters);
}
}
}
}
properties = vevent.getAllProperties(key);
for (pKey in properties) {
if (!properties.hasOwnProperty(pKey)) {
continue;
}
groupId = properties[pKey].getParameter('x-oc-group-id');
if (groupId === null) {
continue;
}
if (oldGroups.indexOf(groupId) !== -1) {
delete properties[pKey];
}
}
},
_readParameters: function(prop, simple, para) {
if (!para) {
return;
}
if (!simple.parameters) {
return;
}
for (var i=0,l=para.length; i < l; i++) {
if (simple.parameters[para[i]]) {
prop.setParameter(para[i], simple.parameters[para[i]]);
} else {
prop.removeParameter(simple.parameters[para[i]]);
}
}
},
_setProperty: function(prop, value, isMultiValue) {
if (isMultiValue) {
prop.setValues(value);
} else {
prop.setValue(value);
}
}
};
/**
* properties supported by event editor
*/
var simpleProperties = {
//General
'summary': {parser: simpleParser.string, reader: simpleReader.string},
'x-oc-calid': {parser: simpleParser.string, reader: simpleReader.string},
'location': {parser: simpleParser.string, reader: simpleReader.string},
'created': {parser: simpleParser.date, reader: simpleReader.date},
'last-modified': {parser: simpleParser.date, reader: simpleReader.date},
'categories': {parser: simpleParser.strings, reader: simpleReader.strings},
//attendees
'attendee': {parser: simpleParser.strings, reader: simpleReader.strings, parameters: attendeeParameters},
'organizer': {parser: simpleParser.string, reader: simpleReader.string},
//sharing
'x-oc-cruds': {parser: simpleParser.string, reader: simpleReader.string},
'class': {parser: simpleParser.string, reader: simpleReader.string},
//other
'description': {parser: simpleParser.string, reader: simpleReader.string},
'url': {parser: simpleParser.string, reader: simpleReader.string},
'status': {parser: simpleParser.string, reader: simpleReader.string},
'resources': {parser: simpleParser.strings, reader: simpleReader.strings}
};
function addZero(t) {
if (t < 10) {
t = '0' + t;
}
return t;
}
function formatDate(d) {
return d.getFullYear() + '-' +
addZero(d.getMonth()) + '-' +
addZero(d.getDate());
}
function formatTime(d) {
return addZero(d.getHours()) + ':' +
addZero(d.getMinutes()) + ':' +
addZero(d.getSeconds());
}
/**
* specific parsers that check only one property
*/
var specificParser = {
alarm: function(data, vevent) {
data.alarm = data.alarm || [];
var alarms = vevent.getAllSubcomponents('valarm'),
group = 0;
for (var key in alarms) {
if (!alarms.hasOwnProperty(key)) {
continue;
}
var alarm = alarms[key];
var alarmData = {
group: group,
action: {},
trigger: {},
repeat: {},
duration: {},
attendee: []
};
simpleParser.string(alarmData, alarm, 'action');
simpleParser.date(alarmData, alarm, 'trigger');
simpleParser.string(alarmData, alarm, 'repeat');
simpleParser.string(alarmData, alarm, 'duration');
simpleParser.strings(alarmData, alarm, 'attendee', attendeeParameters);
if (alarm.hasProperty('trigger')) {
var trigger = alarm.getFirstProperty('trigger');
var related = trigger.getParameter('related');
if (related) {
alarmData.trigger.related = related;
} else {
alarmData.trigger.related = 'start';
}
}
data.alarm.push(alarmData);
alarm.getFirstProperty('action')
.setParameter('x-oc-group-id', group.toString());
group++;
}
},
date: function(data, vevent) {
var dtstart = vevent.getFirstPropertyValue('dtstart');
var dtend;
if (vevent.hasProperty('dtend')) {
dtend = vevent.getFirstPropertyValue('dtend');
} else if (vevent.hasProperty('duration')) {
dtend = dtstart.clone();
dtend.addDuration(vevent.getFirstPropertyValue('dtstart'));
} else {
dtend = dtstart.clone();
}
data.dtstart = {
date: formatDate(dtstart.toJSDate()),
time: formatTime(dtstart.toJSDate()),
type: dtstart.icaltype,
zone: dtstart.zone.toString()
};
data.dtend = {
date: formatDate(dtend.toJSDate()),
time: formatTime(dtend.toJSDate()),
type: dtend.icaltype,
zone: dtend.zone.toString()
};
data.allDay = (dtstart.icaltype === 'date' && dtend.icaltype === 'date');
},
repeating: function(data, vevent) {
var iCalEvent = new ICAL.Event(vevent);
data.repeating = iCalEvent.isRecurring();
simpleParser.dates(data, vevent, 'rdate');
simpleParser.string(data, vevent, 'rrule');
simpleParser.dates(data, vevent, 'exdate');
}
};
var specificReader = {
alarm: function(vevent, oldSimpleData, newSimpleData) {
var oldGroups=[], components=null, cKey=null, groupId, key='alarm';
oldSimpleData[key] = oldSimpleData[key] || [];
for (var i=0, oldLength=oldSimpleData[key].length; i < oldLength; i++) {
oldGroups.push(oldSimpleData[key][i].group);
}
newSimpleData[key] = newSimpleData[key] || [];
for (var j=0, newLength=newSimpleData[key].length; j < newLength; j++) {
var valarm;
if (oldGroups.indexOf(newSimpleData[key][j].group) === -1) {
valarm = new ICAL.Component('VALARM');
vevent.addSubcomponent(valarm);
} else {
oldGroups.splice(oldGroups.indexOf(newSimpleData[key][j].group), 1);
components = vevent.getAllSubcomponents('VALARM');
for (cKey in components) {
if (!components.hasOwnProperty(cKey)) {
continue;
}
groupId = components[cKey].getFirstProperty('action').getParameter('x-oc-group-id');
if (groupId === null) {
continue;
}
if (groupId === newSimpleData[key][j].group) {
valarm = components[cKey];
}
}
}
simpleReader.string(valarm, {}, newSimpleData[key][j], 'action', []);
simpleReader.date(valarm, {}, newSimpleData[key][j], 'trigger', []);
simpleReader.string(valarm, {}, newSimpleData[key][j], 'repeat', []);
simpleReader.string(valarm, {}, newSimpleData[key][j], 'duration', []);
simpleReader.strings(valarm, {}, newSimpleData[key][j], 'attendee', attendeeParameters);
}
},
date: function(vevent, oldSimpleData, newSimpleData) {
delete vevent.dstart;
delete vevent.dtend;
delete vevent.duration;
var parseIntWrapper = function(str) {
return parseInt(str);
};
if (!ICAL.TimezoneService.has(newSimpleData.dtstart.zone)) {
throw {
kind: 'timezone_missing',
missing_timezone: newSimpleData.dtstart.zone
};
}
if (!ICAL.TimezoneService.has(newSimpleData.dtend.zone)) {
throw {
kind: 'timezone_missing',
missing_timezone: newSimpleData.dtend.zone
};
}
var dtstartDateParts = newSimpleData.dtstart.date.split('-').map(parseIntWrapper);
var dtstartTimeParts = newSimpleData.dtstart.time.split(':').map(parseIntWrapper);
var dtstartTz = ICAL.TimezoneService.get(newSimpleData.dtstart.zone);
var start = new ICAL.Time({
year: dtstartDateParts[0],
month: dtstartDateParts[1],
day: dtstartDateParts[2],
hour: dtstartTimeParts[0],
minute: dtstartTimeParts[1],
second: dtstartTimeParts[2],
isDate: newSimpleData.allDay
}, dtstartTz);
var dtendDateParts = newSimpleData.dtend.date.split('-').map(parseIntWrapper);
var dtendTimeParts = newSimpleData.dtend.time.split(':').map(parseIntWrapper);
var dtendTz = ICAL.TimezoneService.get(newSimpleData.dtend.zone);
var end = new ICAL.Time({
year: dtendDateParts[0],
month: dtendDateParts[1],
day: dtendDateParts[2],
hour: dtendTimeParts[0],
minute: dtendTimeParts[1],
second: dtendTimeParts[2],
isDate: newSimpleData.allDay
}, dtendTz);
var dtstart = new ICAL.Property('dtstart', vevent);
dtstart.setValue(start);
dtstart.setParameter('tzid', dtstartTz.tzid);
var dtend = new ICAL.Property('dtend', vevent);
dtend.setValue(end);
dtend.setParameter('tzid', dtendTz.tzid);
vevent.addProperty(dtstart);
vevent.addProperty(dtend);
},
repeating: function(vevent, oldSimpleData, newSimpleData) {
// We won't support exrule, because it's deprecated and barely used in the wild
if (newSimpleData.repeating === false) {
delete vevent.rdate;
delete vevent.rrule;
delete vevent.exdate;
return;
}
simpleReader.dates(vevent, oldSimpleData, newSimpleData, 'rdate');
simpleReader.string(vevent, oldSimpleData, newSimpleData, 'rrule');
simpleReader.dates(vevent, oldSimpleData, newSimpleData, 'exdate');
}
};
return {
/**
* parse and expand jCal data to simple structure
* @param vevent object to be parsed
* @returns {{}}
*/
parse: function(vevent) {
var data=angular.extend({}, defaults), parser, parameters;
for (parser in specificParser) {
if (!specificParser.hasOwnProperty(parser)) {
continue;
}
specificParser[parser](data, vevent);
}
for (var key in simpleProperties) {
if (!simpleProperties.hasOwnProperty(key)) {
continue;
}
parser = simpleProperties[key].parser;
parameters = simpleProperties[key].parameters;
if (vevent.hasProperty(key)) {
parser(data, vevent, key, parameters);
}
}
return data;
},
/**
* patch vevent with data from event editor
* @param vevent object to update
* @param oldSimpleData
* @param newSimpleData
* @returns {*}
*/
patch: function(vevent, oldSimpleData, newSimpleData) {
var key, reader, parameters;
oldSimpleData = angular.extend({}, defaults, oldSimpleData);
newSimpleData = angular.extend({}, defaults, newSimpleData);
for (key in simpleProperties) {
if (!simpleProperties.hasOwnProperty(key)) {
continue;
}
reader = simpleProperties[key].reader;
parameters = simpleProperties[key].parameters;
if (oldSimpleData[key] !== newSimpleData[key]) {
if (newSimpleData === null) {
delete vevent[key];
} else {
reader(vevent, oldSimpleData, newSimpleData, key, parameters);
}
}
}
for (key in specificReader) {
if (!specificReader.hasOwnProperty(key)) {
continue;
}
reader = specificReader[key];
reader(vevent, oldSimpleData, newSimpleData);
}
}
};
});