summaryrefslogtreecommitdiffstats
path: root/js/service
diff options
context:
space:
mode:
Diffstat (limited to 'js/service')
-rw-r--r--js/service/FeedResource.js297
-rw-r--r--js/service/FolderResource.js105
-rw-r--r--js/service/ItemResource.js213
-rw-r--r--js/service/Loading.js27
-rw-r--r--js/service/OPMLImporter.js86
-rw-r--r--js/service/OPMLParser.js72
-rw-r--r--js/service/Publisher.js44
-rw-r--r--js/service/Resource.js90
-rw-r--r--js/service/SettingsResource.js72
9 files changed, 1006 insertions, 0 deletions
diff --git a/js/service/FeedResource.js b/js/service/FeedResource.js
new file mode 100644
index 000000000..0b00019e6
--- /dev/null
+++ b/js/service/FeedResource.js
@@ -0,0 +1,297 @@
+/**
+ * ownCloud - News
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @copyright Bernhard Posselt 2014
+ */
+app.factory('FeedResource', function (Resource, $http, BASE_URL, $q) {
+ 'use strict';
+
+ var FeedResource = function ($http, BASE_URL, $q) {
+ Resource.call(this, $http, BASE_URL, 'url');
+ this.ids = {};
+ this.unreadCount = 0;
+ this.folderUnreadCount = {};
+ this.folderIds = {};
+ this.$q = $q;
+ };
+
+ FeedResource.prototype = Object.create(Resource.prototype);
+
+ FeedResource.prototype.receive = function (data) {
+ Resource.prototype.receive.call(this, data);
+ this.updateUnreadCache();
+ this.updateFolderCache();
+ };
+
+
+ FeedResource.prototype.updateUnreadCache = function () {
+ this.unreadCount = 0;
+ this.folderUnreadCount = {};
+
+ var self = this;
+ this.values.forEach(function (feed) {
+ if (feed.unreadCount) {
+ self.unreadCount += feed.unreadCount;
+ }
+ if (feed.folderId !== undefined) {
+ self.folderUnreadCount[feed.folderId] =
+ self.folderUnreadCount[feed.folderId] || 0;
+ self.folderUnreadCount[feed.folderId] += feed.unreadCount;
+ }
+ });
+ };
+
+
+ FeedResource.prototype.updateFolderCache = function () {
+ this.folderIds = {};
+
+ var self = this;
+ this.values.forEach(function (feed) {
+ self.folderIds[feed.folderId] =
+ self.folderIds[feed.folderId] || [];
+ self.folderIds[feed.folderId].push(feed);
+ });
+ };
+
+
+ FeedResource.prototype.add = function (value) {
+ Resource.prototype.add.call(this, value);
+ if (value.id !== undefined) {
+ this.ids[value.id] = this.hashMap[value.url];
+ }
+ };
+
+
+ FeedResource.prototype.markRead = function () {
+ this.values.forEach(function (feed) {
+ feed.unreadCount = 0;
+ });
+
+ this.unreadCount = 0;
+ this.folderUnreadCount = {};
+ };
+
+
+ FeedResource.prototype.markFeedRead = function (feedId) {
+ this.ids[feedId].unreadCount = 0;
+ this.updateUnreadCache();
+ };
+
+
+ FeedResource.prototype.markFolderRead = function (folderId) {
+ this.values.forEach(function (feed) {
+ if (feed.folderId === folderId) {
+ feed.unreadCount = 0;
+ }
+ });
+
+ this.updateUnreadCache();
+ };
+
+
+ FeedResource.prototype.markItemOfFeedRead = function (feedId) {
+ this.ids[feedId].unreadCount -= 1;
+ this.updateUnreadCache();
+ };
+
+
+ FeedResource.prototype.markItemsOfFeedsRead = function (feedIds) {
+ var self = this;
+ feedIds.forEach(function (feedId) {
+ self.ids[feedId].unreadCount -= 1;
+ });
+
+ this.updateUnreadCache();
+ };
+
+
+ FeedResource.prototype.markItemOfFeedUnread = function (feedId) {
+ this.ids[feedId].unreadCount += 1;
+ this.updateUnreadCache();
+ };
+
+
+ FeedResource.prototype.getUnreadCount = function () {
+ return this.unreadCount;
+ };
+
+
+ FeedResource.prototype.getFolderUnreadCount = function (folderId) {
+ return this.folderUnreadCount[folderId];
+ };
+
+
+ FeedResource.prototype.getByFolderId = function (folderId) {
+ return this.folderIds[folderId] || [];
+ };
+
+
+ FeedResource.prototype.getById = function (feedId) {
+ return this.ids[feedId];
+ };
+
+
+ FeedResource.prototype.rename = function (id, title) {
+ return this.http({
+ method: 'POST',
+ url: this.BASE_URL + '/feeds/' + id + '/rename',
+ data: {
+ feedTitle: title
+ }
+ });
+ };
+
+
+ FeedResource.prototype.move = function (feedId, folderId) {
+ var feed = this.getById(feedId);
+ feed.folderId = folderId;
+
+ this.updateFolderCache();
+ this.updateUnreadCache();
+
+ return this.http({
+ method: 'POST',
+ url: this.BASE_URL + '/feeds/' + feed.id + '/move',
+ data: {
+ parentFolderId: folderId
+ }
+ });
+
+ };
+
+
+ FeedResource.prototype.create = function (url, folderId, title) {
+ url = url.trim();
+ if (!url.startsWith('http')) {
+ url = 'http://' + url;
+ }
+
+ if (title !== undefined) {
+ title = title.trim();
+ }
+
+ var feed = {
+ url: url,
+ folderId: folderId || 0,
+ title: title || url,
+ unreadCount: 0
+ };
+
+ this.add(feed);
+ this.updateFolderCache();
+
+ var deferred = this.$q.defer();
+
+ this.http({
+ method: 'POST',
+ url: this.BASE_URL + '/feeds',
+ data: {
+ url: url,
+ parentFolderId: folderId || 0,
+ title: title
+ }
+ }).success(function (data) {
+ deferred.resolve(data);
+ }).error(function (data) {
+ feed.faviconLink = '';
+ feed.error = data.message;
+ deferred.reject();
+ });
+
+ return deferred.promise;
+ };
+
+
+ FeedResource.prototype.reversiblyDelete = function (id, updateCache) {
+ var feed = this.getById(id);
+
+ if (feed) {
+ feed.deleted = true;
+ }
+
+ if (updateCache !== false) {
+ this.updateUnreadCache();
+ }
+
+ return this.http.delete(this.BASE_URL + '/feeds/' + id);
+ };
+
+
+ FeedResource.prototype.reversiblyDeleteFolder = function (folderId) {
+ var self = this;
+ var promises = [];
+ this.getByFolderId(folderId).forEach(function (feed) {
+ promises.push(self.reversiblyDelete(feed.id, false));
+ });
+
+ this.updateUnreadCache();
+
+ var deferred = this.$q.all(promises);
+ return deferred.promise;
+ };
+
+
+ FeedResource.prototype.delete = function (url, updateCache) {
+ var feed = this.get(url);
+ if (feed.id) {
+ delete this.ids[feed.id];
+ }
+
+ Resource.prototype.delete.call(this, url);
+
+ if (updateCache !== false) {
+ this.updateUnreadCache();
+ this.updateFolderCache();
+ }
+
+ return feed;
+ };
+
+
+ FeedResource.prototype.deleteFolder = function (folderId) {
+ var self = this;
+ this.getByFolderId(folderId).forEach(function (feed) {
+ self.delete(feed.url, false);
+ });
+
+ this.updateUnreadCache();
+ this.updateFolderCache();
+ };
+
+
+ FeedResource.prototype.undoDelete = function (id, updateCache) {
+ var feed = this.getById(id);
+
+ if (feed) {
+ feed.deleted = false;
+ }
+
+ if (updateCache !== false) {
+ this.updateUnreadCache();
+ }
+
+ return this.http.post(this.BASE_URL + '/feeds/' + id + '/restore');
+ };
+
+
+ FeedResource.prototype.undoDeleteFolder = function (folderId) {
+ var self = this;
+ var promises = [];
+
+ this.getByFolderId(folderId).forEach(function (feed) {
+ promises.push(self.undoDelete(feed.id, false));
+ });
+
+ this.updateUnreadCache();
+
+ var deferred = this.$q.all(promises);
+ return deferred.promise;
+ };
+
+
+ return new FeedResource($http, BASE_URL, $q);
+}); \ No newline at end of file
diff --git a/js/service/FolderResource.js b/js/service/FolderResource.js
new file mode 100644
index 000000000..997ba1652
--- /dev/null
+++ b/js/service/FolderResource.js
@@ -0,0 +1,105 @@
+/**
+ * ownCloud - News
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @copyright Bernhard Posselt 2014
+ */
+app.factory('FolderResource', function (Resource, $http, BASE_URL, $q) {
+ 'use strict';
+
+ var FolderResource = function ($http, BASE_URL, $q) {
+ Resource.call(this, $http, BASE_URL, 'name');
+ this.deleted = null;
+ this.$q = $q;
+ };
+
+ FolderResource.prototype = Object.create(Resource.prototype);
+
+
+ FolderResource.prototype.toggleOpen = function (folderName) {
+ var folder = this.get(folderName);
+ folder.opened = !folder.opened;
+
+ return this.http({
+ url: this.BASE_URL + '/folders/' + folder.id + '/open',
+ method: 'POST',
+ data: {
+ folderId: folder.id,
+ open: folder.opened
+ }
+ });
+ };
+
+
+ FolderResource.prototype.rename = function (folderName, toFolderName) {
+ var folder = this.get(folderName);
+ var deferred = this.$q.defer();
+ var self = this;
+
+ this.http({
+ url: this.BASE_URL + '/folders/' + folder.id + '/rename',
+ method: 'POST',
+ data: {
+ folderName: toFolderName
+ }
+ }).success(function () {
+ folder.name = toFolderName;
+ delete self.hashMap[folderName];
+ self.hashMap[toFolderName] = folder;
+
+ deferred.resolve();
+ }).error(function (data) {
+ deferred.reject(data.message);
+ });
+
+ return deferred.promise;
+ };
+
+
+ FolderResource.prototype.create = function (folderName) {
+ folderName = folderName.trim();
+ var folder = {
+ name: folderName
+ };
+
+ this.add(folder);
+
+ var deferred = this.$q.defer();
+
+ this.http({
+ url: this.BASE_URL + '/folders',
+ method: 'POST',
+ data: {
+ folderName: folderName
+ }
+ }).success(function (data) {
+ deferred.resolve(data);
+ }).error(function (data) {
+ folder.error = data.message;
+ });
+
+ return deferred.promise;
+ };
+
+
+ FolderResource.prototype.reversiblyDelete = function (name) {
+ var folder = this.get(name);
+ var id = folder.id;
+ folder.deleted = true;
+ return this.http.delete(this.BASE_URL + '/folders/' + id);
+ };
+
+
+ FolderResource.prototype.undoDelete = function (name) {
+ var folder = this.get(name);
+ var id = folder.id;
+ folder.deleted = false;
+ return this.http.post(this.BASE_URL + '/folders/' + id + '/restore');
+ };
+
+
+ return new FolderResource($http, BASE_URL, $q);
+}); \ No newline at end of file
diff --git a/js/service/ItemResource.js b/js/service/ItemResource.js
new file mode 100644
index 000000000..df4f1b545
--- /dev/null
+++ b/js/service/ItemResource.js
@@ -0,0 +1,213 @@
+/**
+ * ownCloud - News
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @copyright Bernhard Posselt 2014
+ */
+app.factory('ItemResource', function (Resource, $http, BASE_URL,
+ ITEM_BATCH_SIZE) {
+ 'use strict';
+
+ var ItemResource = function ($http, BASE_URL, ITEM_BATCH_SIZE) {
+ Resource.call(this, $http, BASE_URL);
+ this.batchSize = ITEM_BATCH_SIZE;
+ this.clear();
+ };
+
+ ItemResource.prototype = Object.create(Resource.prototype);
+
+ ItemResource.prototype.clear = function () {
+ this.starredCount = 0;
+ this.lowestId = 0;
+ this.highestId = 0;
+ Resource.prototype.clear.call(this);
+ };
+
+ ItemResource.prototype.receive = function (value, channel) {
+ switch (channel) {
+
+ case 'newestItemId':
+ this.newestItemId = value;
+ break;
+
+ case 'starred':
+ this.starredCount = value;
+ break;
+
+ default:
+ var self = this;
+ value.forEach(function (item) {
+ // initialize lowest and highest id
+ if (self.lowestId === 0) {
+ self.lowestId = item.id;
+ }
+ if (self.highestId === 0) {
+ self.highestId = item.id;
+ }
+
+ if (item.id > self.highestId) {
+ self.highestId = item.id;
+ }
+ if (item.id < self.lowestId) {
+ self.lowestId = item.id;
+ }
+ });
+
+ Resource.prototype.receive.call(this, value, channel);
+ }
+ };
+
+
+ ItemResource.prototype.getNewestItemId = function () {
+ return this.newestItemId;
+ };
+
+
+ ItemResource.prototype.getStarredCount = function () {
+ return this.starredCount;
+ };
+
+
+ ItemResource.prototype.star = function (itemId, isStarred) {
+ if (isStarred === undefined) {
+ isStarred = true;
+ }
+
+ var it = this.get(itemId);
+ var url = this.BASE_URL +
+ '/items/' + it.feedId + '/' + it.guidHash + '/star';
+
+ it.starred = isStarred;
+
+ if (isStarred) {
+ this.starredCount += 1;
+ } else {
+ this.starredCount -= 1;
+ }
+
+ return this.http({
+ url: url,
+ method: 'POST',
+ data: {
+ isStarred: isStarred
+ }
+ });
+ };
+
+
+ ItemResource.prototype.toggleStar = function (itemId) {
+ if (this.get(itemId).starred) {
+ this.star(itemId, false);
+ } else {
+ this.star(itemId, true);
+ }
+ };
+
+
+ ItemResource.prototype.markItemRead = function (itemId, isRead) {
+ if (isRead === undefined) {
+ isRead = true;
+ }
+
+ this.get(itemId).unread = !isRead;
+
+ return this.http({
+ url: this.BASE_URL + '/items/' + itemId + '/read',
+ method: 'POST',
+ data: {
+ isRead: isRead
+ }
+ });
+ };
+
+
+ ItemResource.prototype.markItemsRead = function (itemIds) {
+ var self = this;
+
+ itemIds.forEach(function(itemId) {
+ self.get(itemId).unread = false;
+ });
+
+ return this.http({
+ url: this.BASE_URL + '/items/read/multiple',
+ method: 'POST',
+ data: {
+ itemIds: itemIds
+ }
+ });
+ };
+
+
+ ItemResource.prototype.markFeedRead = function (feedId, read) {
+ if (read === undefined) {
+ read = true;
+ }
+
+ var items = this.values.filter(function (element) {
+ return element.feedId === feedId;
+ });
+
+ items.forEach(function (item) {
+ item.unread = !read;
+ });
+
+ return this.http.post(this.BASE_URL + '/feeds/' + feedId + '/read', {
+ highestItemId: this.getNewestItemId()
+ });
+ };
+
+
+ ItemResource.prototype.markRead = function () {
+ this.values.forEach(function (item) {
+ item.unread = false;
+ });
+
+ return this.http({
+ url: this.BASE_URL + '/items/read',
+ method: 'POST',
+ data: {
+ highestItemId: this.getNewestItemId()
+ }
+ });
+ };
+
+
+ ItemResource.prototype.autoPage = function (type, id, oldestFirst) {
+ var offset;
+
+ if (oldestFirst) {
+ offset = this.highestId;
+ } else {
+ offset = this.lowestId;
+ }
+
+ return this.http({
+ url: this.BASE_URL + '/items',
+ method: 'GET',
+ params: {
+ type: type,
+ id: id,
+ offset: offset,
+ limit: this.batchSize,
+ oldestFirst: oldestFirst
+ }
+ });
+ };
+
+
+ ItemResource.prototype.importArticles = function (json) {
+ return this.http({
+ url: this.BASE_URL + '/feeds/import/articles',
+ method: 'POST',
+ data: {
+ json: json
+ }
+ });
+ };
+
+
+ return new ItemResource($http, BASE_URL, ITEM_BATCH_SIZE);
+}); \ No newline at end of file
diff --git a/js/service/Loading.js b/js/service/Loading.js
new file mode 100644
index 000000000..eb42655a5
--- /dev/null
+++ b/js/service/Loading.js
@@ -0,0 +1,27 @@
+/**
+ * ownCloud - News
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @copyright Bernhard Posselt 2014
+ */
+app.service('Loading', function () {
+ 'use strict';
+
+ this.loading = {
+ global: false,
+ content: false,
+ autopaging: false
+ };
+
+ this.setLoading = function (area, isLoading) {
+ this.loading[area] = isLoading;
+ };
+
+ this.isLoading = function (area) {
+ return this.loading[area];
+ };
+
+}); \ No newline at end of file
diff --git a/js/service/OPMLImporter.js b/js/service/OPMLImporter.js
new file mode 100644
index 000000000..2edb7921b
--- /dev/null
+++ b/js/service/OPMLImporter.js
@@ -0,0 +1,86 @@
+/**
+ * ownCloud - News
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @copyright Bernhard Posselt 2014
+ */
+app.service('OPMLImporter', function (FeedResource, FolderResource, $q) {
+ 'use strict';
+ var startFeedJob = function (queue) {
+ var deferred = $q.defer();
+
+ if (queue.lenght > 0) {
+ var feed = queue.pop();
+ var url = feed.url;
+ var title = feed.title;
+ var folderId = 0;
+ var folderName = feed.folderName;
+
+ if (folderName !== undefined &&
+ FeedResource.get(folderName) !== undefined) {
+ folderId = FeedResource.get(feed.folderName).id;
+ }
+
+ // make sure to not add already existing feeds
+ if (url !== undefined && FeedResource.get(url) === undefined) {
+ FeedResource.create(url, folderId, title)
+ .finally(function () {
+ startFeedJob(queue);
+ });
+ }
+ } else {
+ deferred.resolve();
+ }
+
+ return deferred.promise;
+ };
+
+ this.importFolders = function (content) {
+ // assumption: folders are fast to create and we dont need a queue for
+ // them
+ var feedQueue = [];
+ var folderPromises = [];
+ content.folders.forEach(function (folder) {
+ if (folder.name !== undefined) {
+ // skip already created folders
+ if (FolderResource.get(folder.name) !== undefined) {
+ folderPromises.push(FolderResource.create(folder.name));
+ }
+
+ folder.feeds.forEach(function (feed) {
+ feed.folderName = folder.name;
+ feedQueue.push(feed);
+ });
+ }
+ });
+ feedQueue = feedQueue.concat(content.feeds);
+
+ var deferred = $q.defer();
+
+ $q.all(folderPromises).finally(function () {
+ deferred.resolve(feedQueue);
+ });
+
+ return deferred.promise;
+ };
+
+ this.importFeedQueue = function (feedQueue, jobSize) {
+ // queue feeds to prevent server slowdown
+ var deferred = $q.defer();
+
+ var jobPromises = [];
+ for (var i=0; i<jobSize; i+=1) {
+ jobPromises.push(startFeedJob(feedQueue));
+ }
+
+ $q.all(jobPromises).then(function () {
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+ };
+
+}); \ No newline at end of file
diff --git a/js/service/OPMLParser.js b/js/service/OPMLParser.js
new file mode 100644
index 000000000..2b5c3e397
--- /dev/null
+++ b/js/service/OPMLParser.js
@@ -0,0 +1,72 @@
+/**
+ * ownCloud - News
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @copyright Bernhard Posselt 2014
+ */
+app.service('OPMLParser', function () {
+ 'use strict';
+
+ var parseOutline = function (outline) {
+ var url = outline.attr('xmlUrl') || outline.attr('htmlUrl');
+ var name = outline.attr('title') || outline.attr('text') || url;
+
+ // folder
+ if (url === undefined) {
+ return {
+ type: 'folder',
+ name: name,
+ feeds: []
+ };
+
+ // feed
+ } else {
+ return {
+ type: 'feed',
+ name: name,
+ url: url
+ };
+ }
+ };
+
+ // there is only one level, so feeds in a folder in a folder should be
+ // attached to the root folder
+ var recursivelyParse = function (level, root, firstLevel) {
+ for (var i=0; i<level.length; i+=1) {
+ var outline = $(level[i]);
+
+ var entry = parseOutline(outline);
+
+ if (entry.type === 'feed') {
+ root.feeds.push(entry);
+ } else {
+
+ // only first level should append folders
+ if (firstLevel) {
+ recursivelyParse(outline.children('outline'), entry, false);
+ root.folders.push(entry);
+ } else {
+ recursivelyParse(outline.children('outline'), root, false);
+ }
+ }
+ }
+
+ return root;
+ };
+
+ this.parse = function (xml) {
+ xml = $.parseXML(xml);
+ var firstLevel = $(xml).find('body > outline');
+
+ var root = {
+ 'feeds': [],
+ 'folders': []
+ };
+
+ return recursivelyParse(firstLevel, root, true);
+ };
+
+}); \ No newline at end of file
diff --git a/js/service/Publisher.js b/js/service/Publisher.js
new file mode 100644
index 000000000..b5d44c264
--- /dev/null
+++ b/js/service/Publisher.js
@@ -0,0 +1,44 @@
+/**
+ * ownCloud - News
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @copyright Bernhard Posselt 2014
+ */
+
+/*jshint undef:false*/
+app.service('Publisher', function () {
+ 'use strict';
+
+ this.channels = {};
+
+ this.subscribe = function (obj) {
+ var self = this;
+
+ return {
+ toChannels: function (channels) {
+ channels.forEach(function (channel) {
+ self.channels[channel] = self.channels[channel] || [];
+ self.channels[channel].push(obj);
+ });
+ }
+ };
+
+ };
+
+ this.publishAll = function (data) {
+ var self = this;
+
+ Object.keys(data).forEach(function (channel) {
+ var listeners = self.channels[channel];
+ if (listeners !== undefined) {
+ listeners.forEach(function (listener) {
+ listener.receive(data[channel], channel);
+ });
+ }
+ });
+ };
+
+}); \ No newline at end of file
diff --git a/js/service/Resource.js b/js/service/Resource.js
new file mode 100644
index 000000000..c2633c83e
--- /dev/null
+++ b/js/service/Resource.js
@@ -0,0 +1,90 @@
+/**
+ * ownCloud - News
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @copyright Bernhard Posselt 2014
+ */
+app.factory('Resource', function () {
+ 'use strict';
+
+ var Resource = function (http, BASE_URL, id) {
+ this.id = id || 'id';
+ this.values = [];
+ this.hashMap = {};
+ this.http = http;
+ this.BASE_URL = BASE_URL;
+ };
+
+
+ Resource.prototype.receive = function (objs) {
+ var self = this;
+ objs.forEach(function (obj) {
+ self.add(obj);
+ });
+ };
+
+
+ Resource.prototype.add = function (obj) {
+ var existing = this.hashMap[obj[this.id]];
+
+ if (existing === undefined) {
+ this.values.push(obj);
+ this.hashMap[obj[this.id]] = obj;
+ } else {
+ // copy values from new to old object if it exists already
+ Object.keys(obj).forEach(function (key) {
+ existing[key] = obj[key];
+ });
+ }
+ };
+
+
+ Resource.prototype.size = function () {
+ return this.values.length;
+ };
+
+
+ Resource.prototype.get = function (id) {
+ return this.hashMap[id];
+ };
+
+
+ Resource.prototype.delete = function (id) {
+ // find index of object that should be deleted
+ var self = this;
+ var deleteAtIndex = this.values.findIndex(function(element) {
+ return element[self.id] === id;
+ });
+
+ if (deleteAtIndex !== undefined) {
+ this.values.splice(deleteAtIndex, 1);
+ }
+
+ if (this.hashMap[id] !== undefined) {
+ delete this.hashMap[id];
+ }
+ };
+
+
+ Resource.prototype.clear = function () {
+ this.hashMap = {};
+
+ // http://stackoverflow.com/questions/1232040
+ // this is the fastes way to empty an array when you want to keep
+ // the reference around
+ while (this.values.length > 0) {
+ this.values.pop();
+ }
+ };
+
+
+ Resource.prototype.getAll = function () {
+ return this.values;
+ };
+
+
+ return Resource;
+}); \ No newline at end of file
diff --git a/js/service/SettingsResource.js b/js/service/SettingsResource.js
new file mode 100644
index 000000000..6051b2918
--- /dev/null
+++ b/js/service/SettingsResource.js
@@ -0,0 +1,72 @@
+/**
+ * ownCloud - News
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @copyright Bernhard Posselt 2014
+ */
+
+ /*jshint unused:false*/
+app.service('SettingsResource', function ($http, BASE_URL) {
+ 'use strict';
+
+ this.settings = {
+ language: 'en',
+ showAll: false,
+ compact: false,
+ oldestFirst: false,
+ preventReadOnScroll: false
+ };
+ this.defaultLanguageCode = 'en';
+ this.supportedLanguageCodes = [
+ 'ar-ma', 'ar', 'bg', 'ca', 'cs', 'cv', 'da', 'de', 'el', 'en-ca',
+ 'en-gb', 'eo', 'es', 'et', 'eu', 'fi', 'fr-ca', 'fr', 'gl', 'he', 'hi',
+ 'hu', 'id', 'is', 'it', 'ja', 'ka', 'ko', 'lv', 'ms-my', 'nb', 'ne',
+ 'nl', 'pl', 'pt-br', 'pt', 'ro', 'ru', 'sk', 'sl', 'sv', 'th', 'tr',
+ 'tzm-la', 'tzm', 'uk', 'zh-cn', 'zh-tw'
+ ];
+
+ this.receive = function (data) {
+ var self = this;
+ Object.keys(data).forEach(function (key) {
+ var value = data[key];
+
+ if (key === 'language') {
+ value = self.processLanguageCode(value);
+ }
+
+ self.settings[key] = value;
+ });
+ };
+
+ this.get = function (key) {
+ return this.settings[key];
+ };
+
+ this.set = function (key, value) {
+ this.settings[key] = value;
+
+ return $http({
+ url: BASE_URL + '/settings',
+ method: 'PUT',
+ data: this.settings
+ });
+ };
+
+ this.processLanguageCode = function (languageCode) {
+ languageCode = languageCode.replace('_', '-').toLowerCase();
+
+ if (this.supportedLanguageCodes.indexOf(languageCode) < 0) {
+ languageCode = languageCode.split('-')[0];
+ }
+