diff --git a/Gruntfile.js b/Gruntfile.js
index 9c2b3ef6fd03f871175fc422cd3d3cbe1ba1bf6c..18e22008e06a8d4673320cc105cbfe4f85e2e16d 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -181,7 +181,8 @@ module.exports = function (grunt) {
'src/jio.storage/indexeddbstorage.js',
'src/jio.storage/cryptstorage.js',
'src/jio.storage/websqlstorage.js',
- 'src/jio.storage/fbstorage.js'
+ 'src/jio.storage/fbstorage.js',
+ 'src/jio.storage/youtubestorage.js'
],
dest: 'dist/<%= pkg.name %>-<%= pkg.version %>.js'
// dest: 'jio.js'
diff --git a/src/jio.storage/youtubestorage.js b/src/jio.storage/youtubestorage.js
new file mode 100644
index 0000000000000000000000000000000000000000..a5d56b25fce84b1b086f1517f89f8d0edc12b73f
--- /dev/null
+++ b/src/jio.storage/youtubestorage.js
@@ -0,0 +1,120 @@
+/**
+ * Youtube (Data) Storage. Type = "youtube".
+ */
+/*global jIO, RSVP, UriTemplate, JSON, Query */
+/*jslint nomen: true*/
+(function (jIO, RSVP, UriTemplate, JSON, Query) {
+ "use strict";
+
+ var GET_URL = "https://www.googleapis.com/youtube/v3/videos?" +
+ "part=snippet,statistics,contentDetails{&id}{&key}",
+ GET_TEMPLATE = UriTemplate.parse(GET_URL),
+ ALLDOCS_URL = "https://www.googleapis.com/youtube/v3/search?" +
+ "part=snippet{&pageToken}&q={search}&type=video&maxResults=10{&key}",
+ ALLDOCS_TEMPLATE = UriTemplate.parse(ALLDOCS_URL);
+
+ function handleError(error, id) {
+ if (error.target && error.target.status === 404) {
+ throw new jIO.util.jIOError("Cannot find document: " + id, 404);
+ }
+ throw error;
+ }
+
+ function getValue(query_list, key) {
+ var i;
+ for (i = 0; i < query_list.length; i += 1) {
+ if (query_list[i].key === key) {
+ return query_list[i].value;
+ }
+ }
+ }
+
+ /**
+ * The JIO Youtube Storage extension
+ *
+ * @class YoutubeStorage
+ * @constructor
+ */
+ function YoutubeStorage(spec) {
+ if (spec === undefined || spec.api_key === undefined ||
+ typeof spec.api_key !== 'string') {
+ throw new TypeError("API Key must be a string " +
+ "which contains more than one character.");
+ }
+ this._api_key = spec.api_key;
+ return;
+ }
+
+ YoutubeStorage.prototype.get = function (id) {
+ var key = this._api_key;
+ return new RSVP.Queue()
+ .push(function () {
+ return jIO.util.ajax({
+ type: "GET",
+ url: GET_TEMPLATE.expand({
+ "id": id,
+ "key": key
+ })
+ });
+ })
+ .push(function (evt) {
+ return JSON.parse(evt.target.response || evt.target.responseText);
+ })
+ .push(undefined, function (error) {
+ if (error.target && error.target.status === 404) {
+ throw new jIO.util.jIOError("Cannot find document: " + id, 404);
+ }
+ throw error;
+ });
+ };
+
+ YoutubeStorage.prototype.buildQuery = function (options) {
+ var key,
+ query;
+ if (options.query === undefined) {
+ throw new jIO.util.jIOError("query parameter is required",
+ 400);
+ }
+ key = this._api_key;
+ query = Query.parseStringToObject(options.query);
+ return new RSVP.Queue()
+ .push(function () {
+ var token,
+ search;
+
+ if (query.type === "simple") {
+ search = query.value;
+ token = "";
+ } else {
+ search = getValue(query.query_list, "q");
+ token = getValue(query.query_list, "token");
+ }
+
+ return jIO.util.ajax({
+ "type": "GET",
+ "url": ALLDOCS_TEMPLATE.expand({
+ "search": search,
+ "key": key,
+ "pageToken": token
+ })
+ });
+ })
+ .push(function (data) {
+ var obj = JSON.parse(data.target.response || data.target.responseText),
+ i;
+ for (i = 0; i < obj.items.length; i += 1) {
+ obj.items[i].value = {};
+ }
+ obj.items.nextPageToken = obj.nextPageToken;
+ return obj.items;
+
+ }, handleError);
+ };
+
+ YoutubeStorage.prototype.hasCapacity = function (name) {
+ return ((name === "list") || (name === "include") || (name === "query"));
+ };
+
+ jIO.addStorage('youtube', YoutubeStorage);
+
+}(jIO, RSVP, UriTemplate, JSON, Query));
diff --git a/test/jio.storage/youtubestorage.tests.js b/test/jio.storage/youtubestorage.tests.js
new file mode 100644
index 0000000000000000000000000000000000000000..aaceba3a0ba92a57e857b55591bfda500500e80e
--- /dev/null
+++ b/test/jio.storage/youtubestorage.tests.js
@@ -0,0 +1,258 @@
+/*jslint nomen: true */
+/*global Blob, sinon, JSON, Query, SimpleQuery, ComplexQuery*/
+(function (jIO, QUnit, sinon, JSON, Query, SimpleQuery, ComplexQuery) {
+ "use strict";
+
+ var test = QUnit.test,
+ stop = QUnit.stop,
+ start = QUnit.start,
+ ok = QUnit.ok,
+ expect = QUnit.expect,
+ deepEqual = QUnit.deepEqual,
+ equal = QUnit.equal,
+ module = QUnit.module,
+ query = "",
+ api_key = "sample_key",
+ sample_id = "sample_id",
+ token = "sample_token",
+ domain = "https://www.googleapis.com/youtube/v3/",
+ get_reply = {
+ "kind": "youtube#videoListResponse",
+ "pageInfo": {"totalResults": 1, "resultsPerPage": 1},
+ "items": [{"kind": "youtube#video", "id": "sample_id"}]
+ };
+
+ function getSample() {
+ return JSON.stringify({
+ "nextPageToken": "sample_token",
+ "items": [{"id": "0B4kh3jbjOf5LamRlX21MZ"}]
+ });
+ }
+ function getSampleCont() {
+ return JSON.stringify({"items": [{"id": "0B4kh3jbjOf5LamRlVCYXM"}]});
+ }
+ function getUrl() {
+ return domain + "videos?part=snippet,statistics,contentDetails" +
+ "&id=" + sample_id + "&key=" + api_key;
+ }
+ function listUrl(token) {
+ return domain + "search?part=snippet&pageToken=" + token +
+ "&q=" + query + "&type=video&maxResults=10&key=" + api_key;
+ }
+ function getItem(id) {
+ return {"id": id, "value": {}};
+ }
+ function setQuery(key, value) {
+ return new SimpleQuery({"key": key, "value": value, "type": "simple"});
+ }
+ function getResponse(item, has_token) {
+ var obj = {"data": {"rows": [], "total_rows": 1}};
+ obj.data.rows.push(item);
+ if (has_token) {
+ obj.nextPageToken = token;
+ }
+ return obj;
+ }
+ function error404Tester(fun, encl, blob) {
+ stop();
+ expect(3);
+
+ this.jio[fun]("inexistent", encl ? "enclosure" : undefined,
+ blob || undefined)
+ .fail(function (error) {
+ ok(error instanceof jIO.util.jIOError);
+ equal(error.message, "Cannot find document: inexistent");
+ equal(error.status_code, 404);
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ }
+
+ /////////////////////////////////////////////////////////////////
+ // Youtube Storage constructor
+ /////////////////////////////////////////////////////////////////
+ module("Youtube Storage.constructor");
+
+ test("create storage", function () {
+ var jio = jIO.createJIO({
+ type: "youtube",
+ api_key: api_key
+ });
+ equal(jio.__type, "youtube");
+ deepEqual(jio.__storage._api_key, api_key);
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // Youtube Storage.get
+ /////////////////////////////////////////////////////////////////
+ module("Youtube Storage.get", {
+ setup: function () {
+
+ this.server = sinon.fakeServer.create();
+ this.server.autoRespond = true;
+ this.server.autoRespondAfter = 5;
+
+ this.jio = jIO.createJIO({
+ type: "youtube",
+ api_key: api_key
+ });
+ },
+ teardown: function () {
+ this.server.restore();
+ delete this.server;
+ }
+ });
+
+ test("get inexistent document", function () {
+ var tester = error404Tester.bind(this);
+ tester("get");
+ });
+
+ test("get document", function () {
+ var url = getUrl(),
+ body = get_reply;
+
+ this.server.respondWith("GET", url, [200, {
+ "Content-Type": "application/json; charset=UTF-8"
+ }, JSON.stringify(body)
+ ]);
+ stop();
+ expect(1);
+
+ this.jio.get(sample_id)
+ .then(function (result) {
+ deepEqual(result, get_reply);
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ /////////////////////////////////////////////////////////////////
+ // Youtube Storage.allDocs
+ /////////////////////////////////////////////////////////////////
+ module("Youtube Storage.allDocs", {
+ setup: function () {
+
+ this.server = sinon.fakeServer.create();
+ this.server.autoRespond = true;
+ this.server.autoRespondAfter = 5;
+
+ this.jio = jIO.createJIO({
+ type: "youtube",
+ api_key: api_key
+ });
+ },
+ teardown: function () {
+ this.server.restore();
+ delete this.server;
+ }
+ });
+ test("allDocs without query", function () {
+ stop();
+ expect(3);
+
+ this.jio.allDocs()
+ .fail(function (error) {
+ ok(error instanceof jIO.util.jIOError);
+ equal(error.message, "query parameter is required");
+ equal(error.status_code, 400);
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ test("get all docs", function () {
+ var object_result_1 = getResponse(getItem("0B4kh3jbjOf5LamRlVCYXM")),
+ server = this.server;
+
+ this.server.respondWith("GET", listUrl(""), [200, {
+ }, getSampleCont()]);
+ stop();
+ expect(7);
+
+ this.jio.allDocs({"query": Query.objectToSearchText(setQuery("q", ""))})
+ .then(function (res) {
+ equal(server.requests.length, 1);
+ equal(server.requests[0].method, "GET");
+ equal(server.requests[0].url, listUrl(""));
+ equal(server.requests[0].status, 200);
+ equal(server.requests[0].requestBody, undefined);
+ equal(server.requests[0].responseText, getSampleCont());
+ deepEqual(res, object_result_1);
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+ test("allDocs with sequential API requests (nextPageToken)", function () {
+ var context = this,
+ server = this.server,
+ object_result_2;
+
+ this.server.respondWith("GET", listUrl("sample_token"), [200, {
+ }, getSampleCont()]);
+ this.server.respondWith("GET", listUrl(""), [200, {
+ }, getSample()]);
+ stop();
+ expect(14);
+
+ object_result_2 = getResponse(getItem("0B4kh3jbjOf5LamRlX21MZ", true));
+
+ context.jio.allDocs({"query": Query.objectToSearchText(setQuery("q", ""))})
+ .then(function (res) {
+ var query;
+ equal(server.requests.length, 1);
+ equal(server.requests[0].method, "GET");
+ equal(server.requests[0].url, listUrl(""));
+ equal(server.requests[0].status, 200);
+ equal(server.requests[0].requestBody, undefined);
+ equal(server.requests[0].responseText, getSample());
+ deepEqual(res, object_result_2);
+
+ // build complex query with query and token
+ query = Query.objectToSearchText(new ComplexQuery({
+ "operator": "AND",
+ "type": "complex",
+ "query_list": [
+ setQuery("q", ""),
+ setQuery("token", token)
+ ]
+ }));
+
+ object_result_2 = getResponse(getItem("0B4kh3jbjOf5LamRlVCYXM"));
+ return context.jio.allDocs({"query": query});
+ })
+ .then(function (res) {
+ equal(server.requests.length, 2);
+ equal(server.requests[1].method, "GET");
+ equal(server.requests[1].url, listUrl(token));
+ equal(server.requests[1].status, 200);
+ equal(server.requests[1].requestBody, undefined);
+ equal(server.requests[1].responseText, getSampleCont());
+ deepEqual(res, object_result_2);
+ })
+ .fail(function (error) {
+ ok(false, error);
+ })
+ .always(function () {
+ start();
+ });
+ });
+
+}(jIO, QUnit, sinon, JSON, Query, SimpleQuery, ComplexQuery));
diff --git a/test/tests.html b/test/tests.html
index 9bdecc94aa2421e17d7a932811ead3c2f628840c..321d67f9c22e6463858ed915f1ce029200a12454 100644
--- a/test/tests.html
+++ b/test/tests.html
@@ -54,6 +54,7 @@
+