Load calendars via DAV

This commit is contained in:
Raimund Schlüßler 2016-02-13 22:20:39 +01:00
parent d2683926a4
commit 95979ec58c
3 changed files with 498 additions and 7 deletions

View file

@ -29,6 +29,15 @@ angular.module('Tasks').factory 'ListsModel',
@_tmpIdCache = {}
super()
insert: (url, props) ->
calendar =
id: '1'
displayname: props['{DAV:}displayname'] || 'Unnamed'
color: props['{http://apple.com/ns/ical/}calendar-color'] || '#1d2d44'
order: parseInt(props['{http://apple.com/ns/ical/}calendar-order']) || 0
console.log(calendar)
@add(calendar)
add: (list, clearCache=true) ->
tmplist = @_tmpIdCache[list.tmpID]

View file

@ -21,12 +21,12 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
###
angular.module('Tasks').factory 'Persistence',
['Request', 'Loading', '$rootScope', '$q',
(Request, Loading, $rootScope, $q) ->
['Request', 'Loading', '$rootScope', '$q', 'CalendarService'
(Request, Loading, $rootScope, $q, CalendarService) ->
class Persistence
constructor: (@_request, @_Loading, @_$rootScope) ->
constructor: (@_request, @_Loading, @_$rootScope, @_CalendarService) ->
init: ->
@deferred = $q.defer()
@ -37,6 +37,7 @@ angular.module('Tasks').factory 'Persistence',
@getCollections()
@getSettings()
@getLists()
@_CalendarService.getAll()
@getTasks('init', 'all', successCallback)
@deferred.promise
@ -388,6 +389,6 @@ angular.module('Tasks').factory 'Persistence',
@_request.post '/apps/tasks/tasks/{taskID}/category/remove', params
return new Persistence(Request, Loading, $rootScope)
return new Persistence(Request, Loading, $rootScope, CalendarService)
]

View file

@ -2033,6 +2033,473 @@
}).call(this);
angular.module('Tasks').service('CalendarService', ['DavClient', 'ListsModel', function(DavClient, ListsModel){
'use strict';
var _this = this;
this._CALENDAR_HOME = null;
this._currentUserPrincipal = null;
this._takenUrls = [];
this._PROPERTIES = [
'{' + DavClient.NS_DAV + '}displayname',
'{' + DavClient.NS_DAV + '}id',
'{' + DavClient.NS_IETF + '}id',
'{' + DavClient.NS_APPLE + '}id',
'{' + DavClient.NS_IETF + '}calendar-description',
'{' + DavClient.NS_IETF + '}calendar-timezone',
'{' + DavClient.NS_APPLE + '}calendar-order',
'{' + DavClient.NS_APPLE + '}calendar-color',
'{' + DavClient.NS_IETF + '}supported-calendar-component-set',
'{' + DavClient.NS_OWNCLOUD + '}calendar-enabled',
'{' + DavClient.NS_DAV + '}acl',
'{' + DavClient.NS_DAV + '}owner',
'{' + DavClient.NS_OWNCLOUD + '}invite'
];
function discoverHome(callback) {
return DavClient.propFind(DavClient.buildUrl(OC.linkToRemoteBase('dav')), ['{' + DavClient.NS_DAV + '}current-user-principal'], 0).then(function(response) {
if (!DavClient.wasRequestSuccessful(response.status)) {
throw "CalDAV client could not be initialized - Querying current-user-principal failed";
}
if (response.body.propStat.length < 1) {
return;
}
var props = response.body.propStat[0].properties;
_this._currentUserPrincipal = props['{' + DavClient.NS_DAV + '}current-user-principal'][0].textContent;
return DavClient.propFind(DavClient.buildUrl(_this._currentUserPrincipal), ['{' + DavClient.NS_IETF + '}calendar-home-set'], 0).then(function (response) {
if (!DavClient.wasRequestSuccessful(response.status)) {
throw "CalDAV client could not be initialized - Querying calendar-home-set failed";
}
if (response.body.propStat.length < 1) {
return;
}
var props = response.body.propStat[0].properties;
_this._CALENDAR_HOME = props['{' + DavClient.NS_IETF + '}calendar-home-set'][0].textContent;
return callback();
});
});
}
function getResponseCodeFromHTTPResponse(t) {
return parseInt(t.split(' ')[1]);
}
this.getAll = function() {
if (this._CALENDAR_HOME === null) {
return discoverHome(function() {
return _this.getAll();
});
}
return DavClient.propFind(DavClient.buildUrl(this._CALENDAR_HOME), this._PROPERTIES, 1).then(function(response) {
var calendars = [];
if (!DavClient.wasRequestSuccessful(response.status)) {
throw "CalDAV client could not be initialized - Querying calendars failed";
}
for (var i = 0; i < response.body.length; i++) {
var body = response.body[i];
if (body.propStat.length < 1) {
continue;
}
_this._takenUrls.push(body.href);
var responseCode = getResponseCodeFromHTTPResponse(body.propStat[0].status);
if (!DavClient.wasRequestSuccessful(responseCode)) {
continue;
}
var doesSupportVEvent = false;
var components = body.propStat[0].properties['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'];
if (components) {
for (var j=0; j < components.length; j++) {
var name = components[j].attributes.getNamedItem('name').textContent.toLowerCase();
if (name === 'vevent') {
doesSupportVEvent = true;
}
}
}
if (!doesSupportVEvent) {
continue;
}
_this._getACLFromResponse(body);
ListsModel.insert(body.href, body.propStat[0].properties);
// calendars.push(calendar);
}
return calendars;
});
};
this.get = function(url) {
if (this._CALENDAR_HOME === null) {
return discoverHome(function() {
return _this.get(url);
});
}
return DavClient.propFind(DavClient.buildUrl(url), this._PROPERTIES, 0).then(function(response) {
var body = response.body;
if (body.propStat.length < 1) {
//TODO - something went wrong
return;
}
var responseCode = getResponseCodeFromHTTPResponse(body.propStat[0].status);
if (!DavClient.wasRequestSuccessful(responseCode)) {
//TODO - something went wrong
return;
}
_this._getACLFromResponse(body);
return '';//new Calendar(body.href, body.propStat[0].properties);
});
};
this.create = function(name, color, components) {
if (this._CALENDAR_HOME === null) {
return discoverHome(function() {
return _this.create(name, color);
});
}
if (typeof components === 'undefined') {
components = ['vevent'];
}
var xmlDoc = document.implementation.createDocument('', '', null);
var cMkcalendar = xmlDoc.createElement('c:mkcalendar');
cMkcalendar.setAttribute('xmlns:c', 'urn:ietf:params:xml:ns:caldav');
cMkcalendar.setAttribute('xmlns:d', 'DAV:');
cMkcalendar.setAttribute('xmlns:a', 'http://apple.com/ns/ical/');
cMkcalendar.setAttribute('xmlns:o', 'http://owncloud.org/ns');
xmlDoc.appendChild(cMkcalendar);
var dSet = xmlDoc.createElement('d:set');
cMkcalendar.appendChild(dSet);
var dProp = xmlDoc.createElement('d:prop');
dSet.appendChild(dProp);
dProp.appendChild(this._createXMLForProperty(xmlDoc, 'displayname', name));
dProp.appendChild(this._createXMLForProperty(xmlDoc, 'enabled', true));
dProp.appendChild(this._createXMLForProperty(xmlDoc, 'color', color));
dProp.appendChild(this._createXMLForProperty(xmlDoc, 'components', components));
var body = cMkcalendar.outerHTML;
var uri = this._suggestUri(name);
var url = this._CALENDAR_HOME + uri + '/';
var headers = {
'Content-Type' : 'application/xml; charset=utf-8'
};
return DavClient.request('MKCALENDAR', url, headers, body).then(function(response) {
if (response.status === 201) {
_this._takenUrls.push(url);
return _this.get(url).then(function(calendar) {
calendar.enabled = true;
return _this.update(calendar);
});
}
});
};
this.update = function(calendar) {
var xmlDoc = document.implementation.createDocument('', '', null);
var dPropUpdate = xmlDoc.createElement('d:propertyupdate');
dPropUpdate.setAttribute('xmlns:c', 'urn:ietf:params:xml:ns:caldav');
dPropUpdate.setAttribute('xmlns:d', 'DAV:');
dPropUpdate.setAttribute('xmlns:a', 'http://apple.com/ns/ical/');
dPropUpdate.setAttribute('xmlns:o', 'http://owncloud.org/ns');
xmlDoc.appendChild(dPropUpdate);
var dSet = xmlDoc.createElement('d:set');
dPropUpdate.appendChild(dSet);
var dProp = xmlDoc.createElement('d:prop');
dSet.appendChild(dProp);
var updatedProperties = calendar.updatedProperties;
calendar.resetUpdatedProperties();
for (var i=0; i < updatedProperties.length; i++) {
dProp.appendChild(this._createXMLForProperty(
xmlDoc,
updatedProperties[i],
calendar[updatedProperties[i]]
));
}
var url = calendar.url;
var body = dPropUpdate.outerHTML;
var headers = {
'Content-Type' : 'application/xml; charset=utf-8'
};
return DavClient.request('PROPPATCH', url, headers, body).then(function(response) {
var responseBody = DavClient.parseMultiStatus(response.body);
console.log(responseBody);
return calendar;
});
};
this.delete = function(calendar) {
return DavClient.request('DELETE', calendar.url, {}, '').then(function(response) {
if (response.status === 204) {
return true;
} else {
// TODO - handle error case
return false;
}
});
};
this.share = function(calendar, shareType, shareWith, writable, existingShare) {
var xmlDoc = document.implementation.createDocument('', '', null);
var oShare = xmlDoc.createElement('o:share');
oShare.setAttribute('xmlns:d', 'DAV:');
oShare.setAttribute('xmlns:o', 'http://owncloud.org/ns');
xmlDoc.appendChild(oShare);
var oSet = xmlDoc.createElement('o:set');
oShare.appendChild(oSet);
var dHref = xmlDoc.createElement('d:href');
if (shareType === OC.Share.SHARE_TYPE_USER) {
dHref.textContent = 'principal:principals/users/';
} else if (shareType === OC.Share.SHARE_TYPE_GROUP) {
dHref.textContent = 'principal:principals/groups/';
}
dHref.textContent += shareWith;
oSet.appendChild(dHref);
var oSummary = xmlDoc.createElement('o:summary');
oSummary.textContent = t('calendar', '{calendar} shared by {owner}', {
calendar: calendar.displayname,
owner: calendar.owner
});
oSet.appendChild(oSummary);
if (writable) {
var oRW = xmlDoc.createElement('o:read-write');
oSet.appendChild(oRW);
}
var headers = {
'Content-Type' : 'application/xml; charset=utf-8',
requesttoken: oc_requesttoken
};
var body = oShare.outerHTML;
return DavClient.request('POST', calendar.url, headers, body).then(function(response) {
if (response.status === 200) {
if (!existingShare) {
if (shareType === OC.Share.SHARE_TYPE_USER) {
calendar.sharedWith.users.push({
id: shareWith,
displayname: shareWith,
writable: writable
});
} else if (shareType === OC.Share.SHARE_TYPE_GROUP) {
calendar.sharedWith.groups.push({
id: shareWith,
displayname: shareWith,
writable: writable
});
}
}
}
});
};
this.unshare = function(calendar, shareType, shareWith) {
var xmlDoc = document.implementation.createDocument('', '', null);
var oShare = xmlDoc.createElement('o:share');
oShare.setAttribute('xmlns:d', 'DAV:');
oShare.setAttribute('xmlns:o', 'http://owncloud.org/ns');
xmlDoc.appendChild(oShare);
var oRemove = xmlDoc.createElement('o:remove');
oShare.appendChild(oRemove);
var dHref = xmlDoc.createElement('d:href');
if (shareType === OC.Share.SHARE_TYPE_USER) {
dHref.textContent = 'principal:principals/users/';
} else if (shareType === OC.Share.SHARE_TYPE_GROUP) {
dHref.textContent = 'principal:principals/groups/';
}
dHref.textContent += shareWith;
oRemove.appendChild(dHref);
var headers = {
'Content-Type' : 'application/xml; charset=utf-8',
requesttoken: oc_requesttoken
};
var body = oShare.outerHTML;
return DavClient.request('POST', calendar.url, headers, body).then(function(response) {
if (response.status === 200) {
if (shareType === OC.Share.SHARE_TYPE_USER) {
calendar.sharedWith.users = calendar.sharedWith.users.filter(function(user) {
return user.id !== shareWith;
});
} else if (shareType === OC.Share.SHARE_TYPE_GROUP) {
calendar.sharedWith.groups = calendar.sharedWith.groups.filter(function(groups) {
return groups.id !== shareWith;
});
}
//todo - remove entry from calendar object
return true;
} else {
return false;
}
});
};
this._createXMLForProperty = function(xmlDoc, propName, value) {
switch(propName) {
case 'enabled':
var oEnabled = xmlDoc.createElement('o:calendar-enabled');
oEnabled.textContent = value ? '1' : '0';
return oEnabled;
case 'displayname':
var dDisplayname = xmlDoc.createElement('d:displayname');
dDisplayname.textContent = value;
return dDisplayname;
case 'order':
var aOrder = xmlDoc.createElement('a:calendar-color');
aOrder.textContent = value;
return aOrder;
case 'color':
var aColor = xmlDoc.createElement('a:calendar-color');
aColor.textContent = value;
return aColor;
case 'components':
var cComponents = xmlDoc.createElement('c:supported-calendar-component-set');
for (var i=0; i < value.length; i++) {
var cComp = xmlDoc.createElement('c:comp');
cComp.setAttribute('name', value[i].toUpperCase());
cComponents.appendChild(cComp);
}
return cComponents;
}
};
this._getACLFromResponse = function(body) {
var canWrite = false;
var acl = body.propStat[0].properties['{' + DavClient.NS_DAV + '}acl'];
if (acl) {
for (var k=0; k < acl.length; k++) {
var href = acl[k].getElementsByTagNameNS('DAV:', 'href');
if (href.length === 0) {
continue;
}
href = href[0].textContent;
if (href !== _this._currentUserPrincipal) {
continue;
}
var writeNode = acl[k].getElementsByTagNameNS('DAV:', 'write');
if (writeNode.length > 0) {
canWrite = true;
}
}
}
body.propStat[0].properties.canWrite = canWrite;
};
this._isUriAlreadyTaken = function(uri) {
return (this._takenUrls.indexOf(this._CALENDAR_HOME + uri + '/') !== -1);
};
this._suggestUri = function(displayname) {
var uri = displayname.toString().toLowerCase()
.replace(/\s+/g, '-') // Replace spaces with -
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
.replace(/\-\-+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, ''); // Trim - from end of text
if (!this._isUriAlreadyTaken(uri)) {
return uri;
}
if (uri.indexOf('-') === -1) {
uri = uri + '-1';
if (!this._isUriAlreadyTaken(uri)) {
return uri;
}
}
while (this._isUriAlreadyTaken(uri)) {
var positionLastDash = uri.lastIndexOf('-');
var firstPart = uri.substr(0, positionLastDash);
var lastPart = uri.substr(positionLastDash + 1);
if (lastPart.match(/^\d+$/)) {
lastPart = parseInt(lastPart);
lastPart++;
uri = firstPart + '-' + lastPart;
} else if (lastPart === '') {
uri = uri + '1';
} else {
uri = uri = '-1';
}
}
return uri;
};
}]);
angular.module('Tasks').service('DavClient', function() {
'use strict';
var client = new dav.Client({
baseUrl: OC.linkToRemote('dav/calendars'),
xmlNamespaces: {
'DAV:': 'd',
'urn:ietf:params:xml:ns:caldav': 'c',
'http://apple.com/ns/ical/': 'aapl',
'http://owncloud.org/ns': 'oc',
'http://calendarserver.org/ns/': 'cs'
}
});
angular.extend(client, {
NS_DAV: 'DAV:',
NS_IETF: 'urn:ietf:params:xml:ns:caldav',
NS_APPLE: 'http://apple.com/ns/ical/',
NS_OWNCLOUD: 'http://owncloud.org/ns',
NS_CALENDARSERVER: 'http://calendarserver.org/ns/',
buildUrl: function(path) {
return window.location.protocol + '//' + window.location.host + path;
},
wasRequestSuccessful: function(status) {
return (status >= 200 && status <= 299);
}
});
return client;
});
(function() {
angular.module('Tasks').factory('Loading', [
function() {
@ -2249,6 +2716,18 @@
ListsModel.__super__.constructor.call(this);
}
ListsModel.prototype.insert = function(url, props) {
var calendar;
calendar = {
id: '1',
displayname: props['{DAV:}displayname'] || 'Unnamed',
color: props['{http://apple.com/ns/ical/}calendar-color'] || '#1d2d44',
order: parseInt(props['{http://apple.com/ns/ical/}calendar-order']) || 0
};
console.log(calendar);
return this.add(calendar);
};
ListsModel.prototype.add = function(list, clearCache) {
var tmplist, updateById, updateByTmpId;
if (clearCache == null) {
@ -2938,13 +3417,14 @@
(function() {
angular.module('Tasks').factory('Persistence', [
'Request', 'Loading', '$rootScope', '$q', function(Request, Loading, $rootScope, $q) {
'Request', 'Loading', '$rootScope', '$q', 'CalendarService', function(Request, Loading, $rootScope, $q, CalendarService) {
var Persistence;
Persistence = (function() {
function Persistence(_request, _Loading, _$rootScope) {
function Persistence(_request, _Loading, _$rootScope, _CalendarService) {
this._request = _request;
this._Loading = _Loading;
this._$rootScope = _$rootScope;
this._CalendarService = _CalendarService;
}
Persistence.prototype.init = function() {
@ -2957,6 +3437,7 @@
this.getCollections();
this.getSettings();
this.getLists();
this._CalendarService.getAll();
this.getTasks('init', 'all', successCallback);
return this.deferred.promise;
};
@ -3465,7 +3946,7 @@
return Persistence;
})();
return new Persistence(Request, Loading, $rootScope);
return new Persistence(Request, Loading, $rootScope, CalendarService);
}
]);