/src/js/qd.js
JavaScript | 8580 lines | 5871 code | 849 blank | 1860 comment | 845 complexity | 6a49530350732425e620f032ff3ebdd0 MD5 | raw file
Possible License(s): Apache-2.0, MIT, BSD-3-Clause, LGPL-2.0
Large files files are truncated, but you can click here to view the full file
- /*
- Copyright (c) 2004-2009, The Dojo Foundation All Rights Reserved.
- Available via Academic Free License >= 2.1 OR the modified BSD license.
- see: http://dojotoolkit.org/license for details
- */
- /*
- This is a compiled version of Dojo, built for deployment and not for
- development. To get an editable version, please visit:
- http://dojotoolkit.org
- for documentation and information on getting the source.
- */
- /*
- Copyright (c) 2004-2009, The Dojo Foundation All Rights Reserved.
- Available via Academic Free License >= 2.1 OR the modified BSD license.
- see: http://dojotoolkit.org/license for details
- */
- /*
- This is a compiled version of Dojo, built for deployment and not for
- development. To get an editable version, please visit:
- http://dojotoolkit.org
- for documentation and information on getting the source.
- */
- if(!dojo._hasResource["qd.services.util"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
- dojo._hasResource["qd.services.util"] = true;
- dojo.provide("qd.services.util");
- (function(){
- var reHexEntity=/&#x([^;]+);/g,
- reDecEntity=/&#([^;]+);/g;
- dojo.mixin(qd.services.util, {
- // summary:
- // A set of utility methods used throughout the Queued service layers.
- prepare: function(/* Object */args, /* dojo.Deferred? */d){
- // summary:
- // Prepare any deferred (or create a new one) and set
- // up the callback/errback pair on it. args.result
- // is the callback, args.error is the errback. Used
- // primarily by the communication services (online/offline).
- var dfd = d || new dojo.Deferred();
- if(args.result){
- dfd.addCallback(function(data, ioArgs){
- args.result.call(args, data, ioArgs);
- });
- }
- if(args.error){
- dfd.addErrback(function(evt, ioArgs){
- args.error.call(args, evt, ioArgs);
- });
- }
- return dfd; // dojo.Deferred
- },
- mixin: function(/* Object */dest, /* Object */override){
- // summary:
- // Custom mixin function that is considered "additive", as
- // opposed to simply overriding.
- // description:
- // Custom mixin function to stamp the properties from override
- // onto dest without clobbering member objects as you would in
- // a shallow copy like dojo.mixin does; this isn't particularly
- // robust or fast, but it works for our title and queue item objects.
- //
- // The basic property handling rules are:
- // - null doesn't overwrite anything, ever
- // - scalars get overwritten by anything, including new scalars
- // - arrays get overwritten by longer arrays or by objects
- // - objects get merged by recursively calling mixin()
- for(k in override){
- if(override[k] === null || override[k] === undefined){ continue; }
- if(dojo.isArray(override[k])){
- if(dojo.isArray(dest[k])){ // the longest array wins!
- if(override[k].length > dest[k].length){
- dest[k] = override[k].slice(0); // make a copy of the override.
- }
- } else {
- if(!dojo.isObject(dest[k])){
- dest[k] = override[k].slice(0);
- }
- }
- }
- else if(dojo.isObject(override[k])){
- if(dest[k] !== null && dojo.isObject(dest[k])){
- dest[k] = qd.services.util.mixin(dest[k], override[k]);
- }else{
- dest[k] = qd.services.util.mixin({}, override[k]);
- }
- }
- else{
- if(dest[k] === null || (!dojo.isArray(dest[k]) && !dojo.isObject(dest[k]))){
- if(!dest[k]){
- dest[k] = override[k];
- } else if (dest[k] && override[k] && dest[k] != override[k]){
- dest[k] = override[k];
- }
- }
- }
- }
- return dest; // Object
- },
- clean: function(/* String */str){
- // summary:
- // Pull out any HTML tags and replace any HTML entities with the
- // proper characters. Used primarily for the description/synopsis
- // of a title coming from one of the Netflix public RSS feeds.
- return str.replace(reHexEntity, function(){ // String
- return String.fromCharCode(parseInt(arguments[1],16));
- })
- .replace(reDecEntity, function(){
- return String.fromCharCode(parseInt(arguments[1],10));
- })
- .replace(/\"\;/g, '"')
- .replace(/\&apos\;/g, "'")
- .replace(/\&\;/g, "&")
- .replace(/<[^>]*>/g, "");
- },
- image: {
- // summary:
- // Helper functions for caching images for offline.
- url: function(url){
- // summary:
- // Return the best url for the image.
- // url: String
- // The Netflix URL to check against the local cache.
- var file = air.File.applicationStorageDirectory.resolvePath(url.replace("http://", ""));
- if(file.exists){
- return file.url;
- }
- return url; // String
- },
- store: function(url){
- // summary:
- // Return the best url for the image.
- // url: String
- // The Netflix URL to store to the local cache.
- var l = new air.URLLoader(), u = new air.URLRequest(url);
- var dfd = new dojo.Deferred();
- l.dataFormat = air.URLLoaderDataFormat.BINARY;
- // save the data once it has completed loading.
- l.addEventListener(air.Event.COMPLETE, function(evt){
- // make sure the cache directory is created
- var tmpUrl = url.replace("http://", "");
- var file = air.File.applicationStorageDirectory.resolvePath(tmpUrl);
- // this branch shouldn't happen but just in case...
- if(file.exists){
- file.deleteFile();
- }
- // open up the file object for writing.
- var fs = new air.FileStream();
- fs.open(file, air.FileMode.WRITE);
- fs.writeBytes(l.data, 0, l.data.length);
- fs.close();
- // fire the callback
- dfd.callback(file.url, url);
- });
- // do something about an error
- l.addEventListener(air.IOErrorEvent.IO_ERROR, function(evt){
- dfd.errback(url, evt);
- });
- // just in case a security error is thrown.
- l.addEventListener(air.SecurityErrorEvent.SECURITY_ERROR, function(evt){
- dfd.errback(url, evt);
- });
- // load the URL.
- l.load(u);
- return dfd; // dojo.Deferred
- }
- }
- });
- })();
- }
- if(!dojo._hasResource["qd.services.storage"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
- dojo._hasResource["qd.services.storage"] = true;
- dojo.provide("qd.services.storage");
- (function(){
- var els = air.EncryptedLocalStore,
- ba = air.ByteArray;
- qd.services.storage = new (function(/* Boolean? */useCompression){
- // summary:
- // A singleton object that acts as the broker to the Encrypted Local Storage of AIR.
- var compress = useCompression || false;
- // basic common functionality
- this.item = function(/* String */key, /* Object? */value){
- // summary:
- // Provide a dojo-like interface for getting and
- // setting items in the Store.
- if(key === null || key === undefined || !key.length){
- throw new Error("qd.services.storage.item: you cannot pass an undefined or empty string as a key.");
- }
- if(value !== undefined){
- // setter branch
- var stream = new ba();
- stream.writeUTFBytes(dojo.toJson(value));
- if(compress){
- stream.compress();
- }
- els.setItem(key, stream);
- return value; // Object
- }
- // getter branch
- var stream = els.getItem(key);
- if(!stream){
- return null; // Object
- }
- if(compress){
- try {
- stream.uncompress();
- } catch(ex){
- // odds are we have an uncompressed thing here, so simply kill it and return null.
- els.removeItem(key);
- return null; // Object
- }
- }
- // just in case, we make sure there's no "undefined" in the pulled JSON.
- var s = stream.readUTFBytes(stream.length).replace("undefined", "null");
- return dojo.fromJson(s); // Object
- };
- this.remove = function(/* String */key){
- // summary:
- // Remove the item at key from the Encrypted Local Storage.
- if(key === null || key === undefined || !key.length){
- throw new Error("qd.services.storage.remove: you cannot pass an undefined or empty string as a key.");
- }
- els.removeItem(key);
- };
- this.clear = function(){
- // summary:
- // Clear out anything in the Encryped Local Storage.
- els.reset();
- this.onClear();
- };
- this.onClear = function(){
- // summary:
- // Stub function to run anything when the storage is cleared.
- };
- })();
- })();
- }
- if(!dojo._hasResource["qd.services.data"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
- dojo._hasResource["qd.services.data"] = true;
- dojo.provide("qd.services.data");
- qd.services.data = new (function(){
- // summary:
- // A singleton object that handles any interaction with the
- // encrypted database.
- // database: String
- // The filename of the database.
- this._testKey = "h1dd3n!!11one1";
- this._testDb = "queued-test.db"; // revert to queued.db
- var _key = this._testKey;
- this.database = this._testDb;
- var initFile = "js/updates/resources/initialize.sql",
- initialized = false;
- var syncConn = new air.SQLConnection(),
- asyncConn = new air.SQLConnection(),
- inCreation = false,
- self = this;
- // Properties
- this.__defineGetter__("initialized", function(){
- // summary:
- // Returns whether the engine is initialized.
- return initialized; // Boolean
- });
- // these can't be getters, unfortunately.
- this.connection = function(/* Boolean? */async){
- // summary:
- // Return the proper connection.
- return (async ? asyncConn : syncConn); // air.SQLConnection
- };
- this.connected = function(/* Boolean? */async){
- // summary:
- // Returns whether the appropriate connection is actually connected.
- return (async ? asyncConn.connected : syncConn.connected ); // Boolean
- };
- this.transacting = function(/* Boolean? */async){
- // summary:
- // Return whether the appropriate connection is in the middle of a transaction.
- return (async ? asyncConn.inTransaction : syncConn.inTransaction ); // Boolean
- };
- this.lastId = function(/* Boolean? */async){
- // summary:
- // Return the lastId of the appropriate connection (INSERT/REPLACE).
- return (async ? asyncConn.lastInsertRowID : syncConn.lastInsertRowID ); // mixed
- };
- function eventSetup(/* air.SQLConnection */conn){
- // set up all of our event handlers on the passed connection
- // open the db, set up the connection handlers
- conn.addEventListener(air.SQLEvent.OPEN, self.onOpen);
- conn.addEventListener(air.SQLErrorEvent.ERROR, self.onError);
- conn.addEventListener(air.SQLEvent.CLOSE, self.onClose);
- conn.addEventListener(air.SQLEvent.ANALYZE, self.onAnalyze);
- conn.addEventListener(air.SQLEvent.DEANALYZE, self.onDeanalyze);
- conn.addEventListener(air.SQLEvent.COMPACT, self.onCompact);
- conn.addEventListener(air.SQLEvent.BEGIN, self.onBegin);
- conn.addEventListener(air.SQLEvent.COMMIT, self.onCommit);
- conn.addEventListener(air.SQLEvent.ROLLBACK, self.onRollback);
- conn.addEventListener(air.SQLEvent.CANCEL, self.onCancel);
- conn.addEventListener(air.SQLUpdateEvent.INSERT, self.onInsert);
- conn.addEventListener(air.SQLUpdateEvent.UPDATE, self.onUpdate);
- conn.addEventListener("delete", self.onDelete);
- return conn;
- }
-
- this.init = function(/* String */key, /* String */db, /* Boolean? */forceCreate){
- // summary:
- // Initialize the Queued data service.
- // set up the key
- var k = key||this._testKey;
- if(typeof(k) == "string"){
- _key = new air.ByteArray();
- _key.writeUTFBytes(k);
- k = _key;
- }
- if(key){ this._testKey = key; }
- this.database = db || this.database;
- // open the sync connection and test to see if it needs to run the create statements
- var sync = eventSetup(syncConn);
- sync.open(air.File.applicationStorageDirectory.resolvePath(this.database), "create", false, 1024, k);
- // open the async connection
- var async = eventSetup(asyncConn);
- async.openAsync(air.File.applicationStorageDirectory.resolvePath(this.database), "create", null, false, 1024, k);
- if(!forceCreate){
- var s = new air.SQLStatement();
- s.sqlConnection = sync;
- // latest change: remove integer ID from Title table. If it exists, recreate.
- s.text = "SELECT json FROM Title LIMIT 1";
- try{
- s.execute();
- } catch(e){
- this.create({ connection: sync, file: this.initFile });
- }
- } else {
- this.create({ connection: sync, file: this.initFile });
- }
- this.onInitialize();
- // attach to app.onExit
- var h = dojo.connect(qd.app, "onExit", function(evt){
- if(evt.isDefaultPrevented()){
- return;
- }
- dojo.disconnect(h);
- async.close();
- sync.close();
- air.trace("Database connections closed.");
- });
- };
- /*=====
- qd.services.data.__CreateArgs = function(file, connection){
- // summary:
- // Optional keyword arguments object for the create method.
- // file: String?
- // The filename to be used for creating the db.
- // connection: air.SQLConnection?
- // The connection to be used for creating the db. Defaults
- // to the synchronous connection.
- }
- =====*/
- this.create = function(/* qd.services.data.__CreateArgs */kwArgs){
- // summary:
- // Create the database.
- inCreation = true;
- var file = kwArgs && kwArgs.file || initFile,
- conn = kwArgs && kwArgs.connection || syncConn;
- var f = air.File.applicationDirectory.resolvePath(file);
- if(f.exists){
- // kill off the async connection first.
- asyncConn.close();
- asyncConn = null;
- var fs = new air.FileStream();
- fs.open(f, air.FileMode.READ);
- var txt = fs.readUTFBytes(fs.bytesAvailable);
- fs.close();
- var st = new Date();
- // break it apart.
- txt = txt.replace(/\t/g, "");
- var c="", inMerge = false, test = txt.split(/\r\n|\r|\n/), a=[];
- for(var i=0; i<test.length; i++){
- if(inMerge){
- c += test[i];
- if(test[i].indexOf(")")>-1){
- a.push(c);
- c = "";
- inMerge = false;
- }
- } else {
- if(test[i].indexOf("(")>-1 && test[i].indexOf(")")==-1){
- inMerge = true;
- c += test[i];
- } else {
- a.push(test[i]);
- }
- }
- }
-
- // use raw SQL statements here because of the need to preempt any
- // statements that might have been called while creating.
- for(var i=0, l=a.length; i<l; i++){
- var item = dojo.trim(a[i]);
- if(!item.length || item.indexOf("--")>-1){ continue; }
- var s = new air.SQLStatement();
- s.text = item;
- s.sqlConnection = conn;
- s.execute();
- }
- // profiling
- console.warn("db creation took " + (new Date().valueOf() - st.valueOf()) + "ms.");
- // re-open the async connection.
- asyncConn = new air.SQLConnection();
- var async = eventSetup(asyncConn);
- async.openAsync(air.File.applicationStorageDirectory.resolvePath(this.database), "create", null, false, 1024, _key);
- // fire off the onCreate event.
- this.onCreate();
- // run an analysis on it
- //conn.analyze();
- }
- };
- /*=====
- qd.services.data.fetch.__Args = function(sql, params, result, error){
- // sql: String
- // The SQL statement to be executed.
- // params: Object|Array?
- // Any parameters to be pushed into the SQL statement. If an
- // Array, expects the SQL statement to be using ?, if an object
- // it expects the SQL statement to be using keywords, prepended
- // with ":".
- // result: Function?
- // The callback to be executed when results are returned.
- // error: Function?
- // The callback to be executed when an error occurs.
- this.sql = sql;
- this.params = params;
- this.result = result;
- this.error = error;
- }
- =====*/
- function prep(/* qd.services.data.fetch.__Args */kwArgs, /* air.SQLStatement */s, /* Boolean */async){
- // summary:
- // Prepare the SQL statement and return it.
- s.sqlConnection = kwArgs.connection || (async ? asyncConn : syncConn);
- s.text = kwArgs.sql;
- if(kwArgs.params && dojo.isArray(kwArgs.params)){
- // allow the ordered list version
- for(var i=0, l=kwArgs.params.length; i<l; i++){
- s.parameters[i] = kwArgs.params[i];
- }
- } else {
- var params = kwArgs.params || {};
- for(var p in params){
- s.parameters[":" + p] = params[p];
- }
- }
- return s; // air.SQLStatement
- }
- var queue = [], createHandler;
- function exec(){
- var o = queue.shift();
- if(o){
- o.deferred.addCallback(exec);
- o.deferred.addErrback(exec);
- o.statement.execute();
- }
- }
- function query(/* qd.services.data.fetch.__Args */kwArgs, /* air.SQLStatement */s){
- // summary:
- // Inner function to communicate with the database.
-
- // set up the deferred.
- var dfd = new dojo.Deferred();
- // set up the event handlers.
- var onResult = function(evt){
- var result = s.getResult();
- dfd.callback(result);
- };
- var onError = function(evt){
- console.warn(evt);
- dfd.errback(evt);
- }
- if(kwArgs.result){
- dfd.addCallback(function(result){
- kwArgs.result.call(kwArgs, result.data, result);
- });
- }
- if(kwArgs.error){
- dfd.addErrback(function(evt){
- kwArgs.error.call(kwArgs, evt);
- });
- }
- s.addEventListener(air.SQLEvent.RESULT, onResult);
- s.addEventListener(air.SQLErrorEvent.ERROR, onError);
- queue.push({
- statement: s,
- deferred: dfd
- });
- if(!inCreation){
- exec();
- }
- else if(!createHandler){
- // we only want this to start once, don't go adding a bunch more connections
- createHandler = dojo.connect(self, "onCreate", function(){
- dojo.disconnect(createHandler);
- createHandler = null;
- exec();
- });
- }
- return dfd; // dojo.Deferred
- }
- this.fetch = function(/* qd.services.data.fetch.__Args */kwArgs){
- // summary:
- // Fetch (i.e. read) data out of the database. Can be used for write operations
- // but is not recommended; use execute for write ops. This method is hard-coded
- // to use the synchronous connection (i.e. thread-blocking).
- if(!kwArgs.sql){
- console.log("qd.services.data.fetch: no SQL passed. " + dojo.toJson(kwArgs));
- return null;
- }
- // fetch should use the sync connection unless an SQLConnection is passed with the kwArgs.
- var s = prep(kwArgs, new air.SQLStatement(), false),
- d = query(kwArgs, s);
- this.onFetch(kwArgs);
- return d; // dojo.Deferred
- };
- this.execute = function(/* qd.services.data.fetch.__Args */kwArgs){
- // summary:
- // Execute the passed SQL against the database. Should be used
- // for write operations (INSERT, REPLACE, DELETE, UPDATE). This
- // method is hard-coded to use the asynchronous connection.
- if(!kwArgs.sql){
- console.log("qd.services.data.execute: no SQL passed. " + dojo.toJson(kwArgs));
- return null;
- }
- // execute should use the async connection unless an SQLConnection is passed with the kwArgs.
- var s = prep(kwArgs, new air.SQLStatement(), true),
- d = query(kwArgs, s);
- this.onExecute(kwArgs);
- return d; // dojo.Deferred
- };
- // event stubs
- this.onError = function(/* air.Event */evt){ };
- this.onOpen = function(/* air.Event */evt){ };
- this.onClose = function(/* air.Event */evt){ };
- // analysis & maintenance
- this.onAnalyze = function(/* air.Event */evt){ };
- this.onDeanalyze = function(/* air.Event */evt){ };
- this.onCompact = function(/* air.Event */evt){ };
- this.onInitialize = function(){ };
- this.onCreate = function(){
- inCreation = false;
- };
- // adding other database files
- this.onAttach = function(/* air.Event */evt){ };
- this.onDetach = function(/* air.Event */evt){ };
- // transactions
- this.onBegin = function(/* air.Event */evt){ };
- this.onCommit = function(/* air.Event */evt){ };
- this.onRollback = function(/* air.Event */evt){ };
- // SQL execution
- this.onFetch = function(/* qd.services.data.fetch.__Args */kwArgs){ };
- this.onExecute = function(/* qd.services.data.fetch.__Args */kwArgs){ };
- this.onCancel = function(/* air.Event */evt){ };
- this.onInsert = function(/* air.Event */evt){ };
- this.onUpdate = function(/* air.Event */evt){ };
- this.onDelete = function(/* air.Event */evt){ };
- })();
- }
- if(!dojo._hasResource["qd.services.network"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
- dojo._hasResource["qd.services.network"] = true;
- dojo.provide("qd.services.network");
- (function(){
- var monitor, monitorUrl="http://www.netflix.com";
- qd.services.network = new (function(){
- // summary:
- // A singleton object for access to the network layer of Queued.
- var self = this,
- pollInterval = 2500;
- var statusChange = function(e){
- self.onChange((monitor && monitor.available));
- };
- // Properties
- this.__defineGetter__("isRunning", function(){
- // summary:
- // Return whether or not the monitor is running.
- return (monitor && monitor.running); // Boolean
- });
- this.__defineGetter__("lastPoll", function(){
- // summary:
- // Return the last time the monitor checked the network status.
- return (monitor && monitor.lastStatusUpdate); // Date
- });
- this.__defineGetter__("available", function(){
- // summary:
- // Return whether or not the network is available.
- return monitor && monitor.available; // Boolean
- });
- this.__defineSetter__("available", function(/* Boolean */b){
- // summary:
- // Explicitly set the network availability
- if(monitor){
- monitor.available = b;
- }
- return (monitor && monitor.available); // Boolean
- });
- // FIXME: This is for DEV purposes only!
- this.offline = function(){
- monitor.stop();
- monitor.available = false;
- };
- this.online = function(){
- monitor.start();
- };
- this.initialized = false;
- // Methods
- this.init = function(/* String? */url){
- // summary:
- // Initialize the network services by creating and starting the monitor.
- // set up the offline monitor
- monitor = new air.URLMonitor(new air.URLRequest((url||monitorUrl)));
- monitor.pollInterval = pollInterval;
- monitor.addEventListener(air.StatusEvent.STATUS, statusChange);
- self.initialized = true;
- self.onInitialize(monitor.urlRequest.url);
- };
- this.start = function(){
- // summary:
- // Start the monitor services.
- if(!monitor){
- self.init();
- }
- console.log("qd.services.network.start: monitor is running.");
- self.onStart();
- return monitor.start();
- };
- this.stop = function(){
- // summary:
- // Stop the monitor services.
- console.log("qd.services.network.stop: monitor is stopped.");
- self.onStop();
- return (monitor && monitor.stop());
- };
- // Event stubs
- this.onInitialize = function(/* String */url){
- // summary:
- // Fires when the network services is initialized.
- qd.app.splash("Network services initialized");
- };
- this.onStart = function(){
- // summary:
- // Fires when the network services is started.
- qd.app.splash("Network services started");
- };
- this.onStop = function(){
- // summary:
- // Fires when the network services is stopped.
- };
- this.onChange = function(/* Boolean */isAvailable){
- // summary:
- // Stub event to connect to when the network status changes
- console.log("qd.services.network.onChange: current status is " + isAvailable);
- };
- })();
- })();
- }
- if(!dojo._hasResource["qd.services.parser"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
- dojo._hasResource["qd.services.parser"] = true;
- dojo.provide("qd.services.parser");
- (function(){
- // summary:
- // A set of objects used to parse any XML returned by the Netflix API.
- var util = qd.services.util;
-
- // TITLES
- var baseTitle = {
- guid: null,
- rentalHistoryGuid: null,
- atHomeGuid: null,
- recommendationGuid: null,
- ratingGuid: null,
- type: null,
- title: null,
- art:{
- small: null,
- medium: null,
- large: null
- },
- releaseYear: null,
- runTime: null,
- rating: null,
- synopsis: null,
- ratings:{ average:null, predicted:null, user:null },
- categories: [],
- bonusMaterials: [],
- awards: {
- nominee:[],
- winner:[]
- },
- formats:{ },
- screenFormats: [],
- cast: [],
- directors: [],
- series: {},
- season: {},
- similars: [],
- audio: {},
- dates: {
- updated: null,
- availability: null
- },
- discs: [],
- episodes: [],
- fullDetail: false
- };
-
- // Parse helpers.
- function parseAwards(nl){
- var ret = [];
- for(var i=0,l=nl.length; i<l; i++){
- var n = nl[i];
- var tmp = {
- year: n.getAttribute("year")
- };
- var c = n.childNodes;
- for(var j=0, jl=c.length; j<jl; j++){
- var cn = c[j];
- if(cn.nodeType==1){
- if(cn.tagName=="category"){
- tmp.scheme = cn.getAttribute("scheme");
- tmp.term = cn.getAttribute("term");
- }
- else if(cn.tagName=="link"){
- var guid = cn.getAttribute("href");
- tmp.person = {
- guid: guid,
- id: guid.substr(guid.lastIndexOf("/")+1),
- title: cn.getAttribute("title")
- };
- }
- }
- }
- ret.push(tmp);
- }
- return ret;
- }
- function parseFormats(nl){
- var ret = {};
- for(var i=0, l=nl.length; i<l; i++){
- var available = nl[i].getAttribute("available_from");
- var d = parseInt(available+"000", 10);
- var n = nl[i].getElementsByTagName("category")[0];
- if(n){
- ret[n.getAttribute("term")] = (available)?new Date(d):null;
- }
- }
- return ret;
- }
- function parseDate(tenDijitStr){
- return dojo.date.locale.format(new Date(Number(tenDijitStr+"000")), {selector:"date", datePattern:"MM/dd/yy"});
- }
- function parseScreenFormats(nl){
- var ret = [];
- for(var i=0, l=nl.length; i<l; i++){
- var categories = nl[i].getElementsByTagName("category"),
- info = { title:"", screen:"" };
- for(var j=0, jl=categories.length; j<jl; j++){
- var c = categories[j];
- if(c.getAttribute("scheme").indexOf("title")>-1){
- info.title = c.getAttribute("term");
- } else {
- info.screen = c.getAttribute("term");
- }
- }
- ret.push(info);
- }
- return ret;
- }
- function parseLinks(nl){
- var ret = [];
- for(var i=0, l=nl.length; i<l; i++){
- var guid = nl[i].getAttribute("href");
- ret.push({
- guid: guid,
- title: nl[i].getAttribute("title")
- });
- }
- return ret;
- }
- function parseAudio(nl){
- var ret = {};
- for(var i=0, l=nl.length; i<l; i++){
- var node = nl[i], tfnode;
- for(var j=0; j<node.childNodes.length; j++){
- if(node.childNodes[j].nodeType != 1){ continue; }
- tfnode = node.childNodes[j];
- break;
- }
- var tf = tfnode.getAttribute("term");
- ret[tf]={ };
- for(var j=0; j<tfnode.childNodes.length; j++){
- if(tfnode.childNodes[j].nodeType != 1){ continue; }
- var lnode = tfnode.childNodes[j],
- tmp = [],
- lang = tfnode.childNodes[j].getAttribute("term");
- for(var k=0; k<lnode.childNodes.length; k++){
- if(lnode.childNodes[k].nodeType != 1){ continue; }
- tmp.push(lnode.childNodes[k].getAttribute("term"));
- }
- ret[tf][lang] = tmp;
- }
- }
- return ret;
- }
-
- // public functions
- var p = qd.services.parser;
- p.titles = {
- // summary:
- // The XML parser for any title information (movie, TV show, etc.)
- fromRss: function(/* XmlNode */node, /* Object? */obj){
- // summary:
- // Parse basic movie information from the passed RSS element.
- var o = dojo.clone(baseTitle);
- for(var i=0, l=node.childNodes.length; i<l; i++){
- var n=node.childNodes[i];
- if(n.nodeType != 1){ continue; } // ignore non-elements
- switch(n.tagName){
- case "title":
- var pieces = dojo.trim(n.textContent).match(/^\d+-\s(.*)$/);
- o.title = pieces ? pieces[1]: dojo.trim(n.textContent);
- break;
- case "guid":
- /*
- // TODO: TV series/seasons detection.
- var guid = dojo.trim(n.textContent);
- // swap out their ID for the real one.
- o.id = guid.substr(guid.lastIndexOf("/")+1);
- o.guid = "http://api.netflix.com/catalog/titles/movies/" + o.id;
- */
- break;
- case "description":
- var pieces = dojo.trim(n.textContent).match(/<img src="([^"]+)".*<br>([^$]*)/);
- if(pieces){
- o.art.small = pieces[1].replace("/small/", "/tiny/");
- o.art.medium = pieces[1];
- o.art.large = pieces[1].replace("/small/", "/large/");
- o.synopsis = util.clean(pieces[2]);
- }
- break;
- }
- }
- if(obj){
- o = util.mixin(obj, o);
- }
- return o; // Object
- },
- fromXml: function(/* XmlNode */node, /* Object?*/obj){
- // summary:
- // Parse the returned title information from the passed XmlNode.
- var o = dojo.clone(baseTitle);
- var links = node.ownerDocument.evaluate("./link", node),
- info = node.ownerDocument.evaluate("./*[name()!='link']", node),
- currentNode;
-
- while(currentNode = info.iterateNext()){
- switch(currentNode.tagName){
- case "id":
- // need to fork this a little because of "other" ids that are
- // possibly passed by Netflix.
- var test = currentNode.parentNode.tagName,
- value = dojo.trim(currentNode.textContent);
- if(test == "ratings_item"){
- o.ratingGuid = value;
- }
- else if(test == "rental_history_item"){
- o.rentalHistoryGuid = value;
- }
- else if(test == "at_home_item"){
- o.atHomeGuid = value;
- }
- else if(test == "recommendation"){
- o.recommendationGuid = value;
- }
- else {
- o.guid = value;
- }
- break;
- case "title":
- o.title = currentNode.getAttribute("regular");
- break;
- case "box_art":
- o.art = {
- small: currentNode.getAttribute("small"),
- medium: currentNode.getAttribute("medium"),
- large: currentNode.getAttribute("large")
- };
- break;
- case "release_year":
- o.releaseYear = dojo.trim(currentNode.textContent);
- break;
- case "runtime":
- o.runTime = parseInt(dojo.trim(currentNode.textContent), 10)/60;
- break;
- case "category":
- var scheme = currentNode.getAttribute("scheme");
- scheme = scheme.substr(scheme.lastIndexOf("/")+1);
- if(scheme == "mpaa_ratings" || scheme == "tv_ratings"){
- o.rating = currentNode.getAttribute("term");
- }
- else if (scheme == "genres"){
- o.categories.push(currentNode.getAttribute("term"));
- }
- break;
- case "user_rating":
- var val = currentNode.getAttribute("value");
- if(val == "not_interested"){
- o.ratings.user = val;
- }else{
- o.ratings.user = parseFloat(dojo.trim(currentNode.textContent), 10);
- }
- break;
- case "predicted_rating":
- o.ratings.predicted = parseFloat(dojo.trim(currentNode.textContent), 10);
- break;
- case "average_rating":
- o.ratings.average = parseFloat(dojo.trim(currentNode.textContent), 10);
- break;
- case "availability_date":
- o.dates.availability = parseDate(currentNode.textContent);
- break;
- case "updated":
- o.dates.updated = parseDate(currentNode.textContent);
- break;
- }
- }
- // do the links now.
- while(currentNode = links.iterateNext()){
- var type = currentNode.getAttribute("title"),
- rel = currentNode.getAttribute("rel");
- switch(rel){
- case "http://schemas.netflix.com/catalog/titles/synopsis":
- o.synopsis = util.clean(dojo.trim(currentNode.textContent));
- break;
- case "http://schemas.netflix.com/catalog/titles/awards":
- o.awards.nominee=parseAwards(currentNode.getElementsByTagName("award_nominee"));
- o.awards.winner=parseAwards(currentNode.getElementsByTagName("award_winner"));
- break;
- case "http://schemas.netflix.com/catalog/titles/format_availability":
- var nodes = currentNode.getElementsByTagName("availability");
- if(nodes && nodes.length){
- o.formats = parseFormats(nodes);
- }
- break;
- case "http://schemas.netflix.com/catalog/titles/screen_formats":
- o.screenFormats = parseScreenFormats(currentNode.getElementsByTagName("screen_format"));
- break;
- case "http://schemas.netflix.com/catalog/people.cast":
- o.cast = parseLinks(currentNode.getElementsByTagName("link"));
- break;
- case "http://schemas.netflix.com/catalog/people.directors":
- o.directors = parseLinks(currentNode.getElementsByTagName("link"));
- break;
- case "http://schemas.netflix.com/catalog/titles/languages_and_audio":
- o.audio = parseAudio(currentNode.getElementsByTagName("language_audio_format"));
- break;
- case "http://schemas.netflix.com/catalog/titles.similars":
- o.similars = parseLinks(currentNode.getElementsByTagName("link"));
- break;
- case "http://schemas.netflix.com/catalog/titles/bonus_materials":
- o.bonusMaterials = parseLinks(currentNode.getElementsByTagName("link"));
- break;
- case "http://schemas.netflix.com/catalog/titles/official_url":
- break;
- case "http://schemas.netflix.com/catalog/title":
- o.guid = currentNode.getAttribute("href");
- o.title = type;
- break;
- case "http://schemas.netflix.com/catalog/titles.series":
- o.series = {
- guid: currentNode.getAttribute("href"),
- title: type
- };
- break;
- case "http://schemas.netflix.com/catalog/titles.season":
- o.season = {
- guid: currentNode.getAttribute("href"),
- title: type
- };
- break;
- case "http://schemas.netflix.com/catalog/titles.discs":
- dojo.query("link", currentNode).forEach(function(disc){
- o.discs.push({
- guid: disc.getAttribute("href"),
- title: disc.getAttribute("title")
- });
- });
- break;
- case "http://schemas.netflix.com/catalog/titles.programs":
- dojo.query("link", currentNode).forEach(function(episode){
- o.episodes.push({
- guid: episode.getAttribute("href"),
- title: episode.getAttribute("title")
- });
- });
- break;
- }
- }
- if(obj){
- o = util.mixin(obj, o);
- }
- this.setType(o);
- o.fullDetail = true; // we have the full details now, so mark it as such.
- return o; // Object
- },
-
- setType: function(/* Object */o){
- // summary:
- // Post-process a parsed title to set a type on it.
- if(o.guid.indexOf("discs")>-1){
- o.type = "disc";
- }
- else if (o.guid.indexOf("programs")>-1){
- o.type = "episode";
- }
- else if (o.guid.indexOf("series")>-1){
- if(o.guid.indexOf("seasons")>-1){
- o.type = "season";
- } else {
- o.type = "series";
- }
- }
- else {
- o.type = "movie"; // generic
- }
- }
- };
- p.queues = {
- // summary:
- // The XML parser for queue information (discs, instant, saved, rental history)
- fromXml: function(/* XmlNode */node, /* Object? */obj){
- // summary:
- // Parse the returned XML into an object to be used by the application.
-
- // object representing a queue item. Note that the title info is
- // deliberately limited.
- var item = {
- queue: "/queues/disc",
- guid: null,
- id: null,
- position: null,
- availability: null,
- updated: null,
- shipped: null,
- watched: null,
- estimatedArrival: null,
- returned: null,
- viewed: null,
- format: null,
- title: {
- guid: null,
- title: null
- }
- };
- var info = node.ownerDocument.evaluate("./*", node), currentNode;
- while(currentNode = info.iterateNext()){
- switch(currentNode.tagName){
- case "id":
- item.guid = dojo.trim(currentNode.textContent);
- item.id = item.guid;
- var l = item.guid.split("/");
- l.pop(); // pull the id off
- item.queue = l.slice(5).join("/");
- break;
- case "position":
- item.position = parseInt(currentNode.textContent, 10);
- break;
- case "category":
- var scheme = currentNode.getAttribute("scheme");
- if(scheme == "http://api.netflix.com/categories/queue_availability"){
- item.availability = dojo.trim(currentNode.textContent);
- }
- else if(scheme == "http://api.netflix.com/categories/title_formats"){
- item.format = currentNode.getAttribute("term");
- }
- break;
- case "updated":
- item.updated = parseDate(currentNode.textContent);
- break;
- case "shipped_date":
- item.shipped = parseDate(currentNode.textContent);
- break;
- case "watched_date":
- item.watched = parseDate(currentNode.textContent);
- break;
- case "estimated_arrival_date":
- item.estimatedArrival = parseDate(currentNode.textContent);
- break;
- case "returned_date":
- item.returned = parseDate(currentNode.textContent);
- case "viewed_time":
- item.viewed = currentNode.textContent;
- break;
- case "link":
- // we only care about the title this represents.
- var rel = currentNode.getAttribute("rel");
- if(rel == "http://schemas.netflix.com/catalog/title"){
- // use the title parser on the main node here for basic info.
- // Note that it is up to the calling code to merge this title's
- // info with any existing info.
- item.title = p.titles.fromXml(node);
- }
- else if(rel == "http://schemas.netflix.com/queues.available"){
- // we do this here because for the available queues, Netflix embeds
- // the position in the guid.
- var l = currentNode.getAttribute("href");
- // redo the id
- item.id = l + "/" + item.guid.substr(item.guid.lastIndexOf("/")+1);
- }
- break;
- }
- }
- if(obj){
- item = util.mixin(obj, item);
- }
- return item; // Object
- }
- };
- p.users = {
- // summary:
- // The XML parser for any user information
- fromXml: function(/* XmlNode */node, /* Object? */obj){
- // summary:
- // Return a user object from the passed xml node.
- var user = {
- name: { first: null, last: null },
- userId: null,
- canInstantWatch: false,
- preferredFormats: []
- };
- // ignore the links included for now.
- var info = node.ownerDocument.evaluate("./*[name()!='link']", node), currentNode;
- while(currentNode = info.iterateNext()){
- switch(currentNode.tagName){
- case "user_id":
- user.userId = dojo.trim(currentNode.textContent);
- break;
- case "first_name":
- user.name.first = dojo.trim(currentNode.textContent);
- break;
- case "last_name":
- user.name.last = dojo.trim(currentNode.textContent);
- break;
- case "can_instant_watch":
- user.canInstantWatch = dojo.trim(currentNode.textContent)=="true";
- break;
- case "preferred_formats":
- dojo.query("category", currentNode).forEach(function(item){
- user.preferredFormats.push(item.getAttribute("term"));
- });
- break;
- }
- }
- if(obj){
- obj = util.mixin(obj, user);
- }
- return user; // Object
- }
- };
- p.people = {
- // summary:
- // The XML parser for any information on a person (actors, directors, etc.)
- fromXml: function(/* XmlNode */node, /* Object? */obj){
- // summary:
- // Parse the information out of the passed XmlNode for people.
- var person = {
- id: null,
- name: null,
- bio: null,
- filmography: null
- };
- var info = node.ownerDocument.evaluate("./name()", node), currentNode;
- while(currentNode = info.iterateNext()){
- switch(currentNode.tagName){
- case "id":
- case "name":
- case "bio":
- person[currentNode.tagName] = util.clean(dojo.trim(currentNode.textContent));
- break;
- case "link":
- // ignore the alternate link
- if(currentNode.getAttribute("rel") == "http://schemas.netflix.com/catalog/titles.filmography"){
- person.filmography = currentNode.getAttribute("href");
- }
- break;
- }
- }
- if(obj){
- person = util.mixin(obj, person);
- }
- return person; // Object
- }
- };
- p.status = {
- // summary:
- // The XML parser for any status-based updates (modifying a queue, ratings, etc.)
- fromXml: function(/* XmlNode */node){
- // summary:
- // Parse the status info out of the passed node.
- var obj = {};
- for(var i=0, l=node.childNodes.length; i<l; i++){
- var item = node.childNodes[i];
- if(item.nodeType == 1){
- switch(item.tagName){
- case "status_code":
- obj.code = dojo.trim(item.textContent);
- break;
- case "sub_code":
- obj.subcode = dojo.trim(item.textContent);
- break;
- case "message":
- case "etag":
- obj[item.tagName] = dojo.trim(item.textContent);
- break;
- case "resources_created":
- obj.created = dojo.query("queue_item", item).map(function(n){
- return p.queues.fromXml(n);
- });
- break;
- case "failed_title_refs":
- obj.failed = dojo.query("link", item).map(function(n){
- return {
- guid: n.getAttribute("href"),
- title: n.getAttribute("title")
- };
- });
- break;
- case "already_in_queue":
- obj.inQueue = dojo.query("link", item).map(function(n){
- return {
- guid: n.getAttribute("href"),
- title: n.getAttribute("title")
- };
- });
- break;
- }
- }
- }
- return obj; // Object
- }
- };
- })();
- }
- if(!dojo._hasResource["qd.services.online.feeds"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
- dojo._hasResource["qd.services.online.feeds"] = true;
- dojo.provide("qd.services.online.feeds");
- (function(){
- var util = qd.services.util,
- ps = qd.services.parser,
- db = qd.services.data;
- var rssFeeds = {
- top25: [],
- top100: null,
- newReleases: null
- };
- var top25Feeds = [], top100Feed, newReleasesFeed;
- var feedlistInit = function(){
- db.fetch({
- sql: "SELECT id, term, lastUpdated, isInstant, feed, xml FROM GenreFeed ORDER BY term",
- result: function(data, result){
- dojo.forEach(data, function(item){
- if(item.feed.indexOf("Top100RSS")>-1){
- rssFeeds.top100 = item;
- } else if(item.feed.indexOf("NewReleasesRSS")>-1){
- rssFeeds.newReleases = item;
- } else {
- rssFeeds.top25.push(item);
- }
- });
- }
- });
- };
- if(db.initialized){
- feedlistInit();
- } else {
- var h = dojo.connect(db, "onInitialize", function(){
- dojo.disconnect(h);
- feedlistInit();
- });
- }
- function getFeedObject(url){
- if(url == rssFeeds.top100.feed){ return rssFeeds.top100; }
- if(url == rssFeeds.newReleases.feed){ return rssFeeds.newReleases; }
- for(var i=0; i<rssFeeds.top25.length; i++){
- if(url == rssFeeds.top25[i].feed){
- return rssFeeds.top25[i];
- }
- }
- return null;
- }
- dojo.mixin(qd.services.online.feeds, {
- // summary:
- // The online-based service for handling Netflix's public RSS feeds.
- list: function(){
- // summary:
- // Return the list of Top 25 RSS feeds.
- return rssFeeds.top25; // Object[]
- },
- top100: function(){
- // summary:
- // Return the top 100 feed object.
- return rssFeeds.top100; // Object
- },
- newReleases: function(){
- // summary:
- // Return the New Releases feed object.
- return rssFeeds.newReleases; // Object
- },
- /*=====
- qd.services.online.feeds.fetch.__FetchArgs = function(url, result, error){
- // summary:
- // Keyword object for getting the contents of an RSS feed.
- // url: String
- // The URL of the feed to fetch.
- // result: Function?
- // The callback to be fired when the RSS feed has been fetched and parsed.
- // error: Function?
- // The errback function to be fired if there is an error during the course of the fetch.
- }
- =====*/
- fetch: function(/* qd.services.online.feeds.fetch.__FetchArgs */kwArgs){
- // summary:
- // Fetch the feed at the url in the feed object, and
- // store/cache the feed when returned.
- var dfd = util.prepare(kwArgs), feed=getFeedObject(kwArgs.url);
- dojo.xhrGet({
- url: kwArgs.url,
- handleAs: "xml",
- load: function(xml, ioArgs){
- var node, parsed = [], items = xml.evaluate("//channel/item", xml);
- while(node = items.iterateNext()){
- parsed.push(ps.titles.fromRss(node));
- }
- // pre and post-process the results (image caching)
- qd.services.online.process(parsed, dfd);
- // push the xml doc into the database
- var sql = "UPDATE GenreFeed SET LastUpdated = DATETIME(), xml=:xml WHERE id=:id ";
- db.execute({
- sql: sql,
- params:{
- id: feed.id,
- xml: new XMLSerializer().serializeToString(xml).replace(/'/g, "''")
- },
- result: function(data){
- // console.log("Stored the feed ("+feed.term+")");
- }
- });
- },
- error: function(err, ioArgs){
- dfd.errback(new Error("qd.service.feeds.fetch: an error occurred when trying to get " + kwArgs.url));
- }
- });
- return dfd; // dojo.Deferred
- }
- });
- })();
- }
- if(!dojo._hasResource["qd.services.online.titles"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
- dojo._hasResource["qd.services.online.titles"] = true;
- dojo.provide("qd.services.online.titles");
- (function(){
- var ps = qd.services.parser,
- db = qd.services.data,
- util = qd.services.util;
- var some = encodeURIComponent("discs,episodes,seasons,synopsis,formats, screen formats"),
- expand = encodeURIComponent("awards,cast,directors,discs,episodes,formats,languages and audio,screen formats,seasons,synopsis"); // similars
- function saveTitle(item){
- var param = {
- guid: item.guid,
- link: item.guid,
- title: item.title,
- synopsis: item.synopsis,
- rating: item.rating,
- item: dojo.toJson(item)
- };
- db.execute({
- sql: "SELECT json FROM Title WHERE guid=:guid",
- params: {
- guid: item.guid
- },
- result: function(data){
- if(data && data.length){
- // mix-in the passed json with the stored one.
- item = util.mixin(dojo.fromJson(data[0].json), item);
- param.item = dojo.toJson(item);
- }
- db.execute({
- sql: "REPLACE INTO Title (guid, link, title, lastUpdated, synopsis, rating, json)"
- + " VALUES(:guid, :link, :title, DATETIME(), :synopsis, :rating, :item)",
- params: param,
- result: function(data){
- // don't need this, keeping it for logging purposes.
- },
- error: function(err){
- console.warn("titles.save: ERROR!", error);
- }
- });
- }
- });
- }
- function fetchItem(url, dfd, term){
- var signer = qd.app.authorization;
- dojo.xhrGet(dojox.io.OAuth.sign("GET", {
- url: url,
- handleAs: "xml",
- load: function(xml, ioArgs){
- // get the right node, parse it, cache it, and callback it.
- var node, items = xml.evaluate((term!==undefined?"//catalog_title":"//ratings/ratings_item"), xml), ob;
- while(node = items.iterateNext()){
- if(term !== undefined){
- var test = node.getElementsByTagName("title");
- if(test.length && test[0].getAttribute("regular") == util.clean(term)){
- ob = ps.titles.fromXml(node);
- break;
- }
- } else {
- ob = ps.titles.fromXml(node);
- break;
- }
- }
- if(ob){
- if(term !== undefined){
- // hit it again, this time with a guid (so we can get some ratings)
- url = "http://api.netflix.com/users/" + signer.userId + "/ratings/title"
- + "?title_refs=" + ob.guid
- + "&expand=" + expand;
- fetchItem(url, dfd);
- } else {
- // fire off the callback, passing it the returned object.
- qd.services.item(ob);
- qd.services.online.process(ob, dfd);
- saveTitle(ob);
- }
- } else {
- // in case we don't get an exact term match back from Netflix.
- var e = new Error("No term match from Netflix for " + term);
- e.xml = xml;
- dfd.errback(e, ioArgs);
- }
- },
- error: function(err, ioArgs){
- // TODO: modify for an invalid signature check.
- var e = new Error(err);
- e.xml = ioArgs.xhr.responseXML;
- dfd.errback(e, ioArgs);
- }
- }, signer), false);
- }
- function batchRatingsFetch(titles, args, ratings){
- // summary:
- // Private function to do actual ratings calls.
- var signer = qd.app.authorization,
- refs= [];
- for(var p in titles){
- refs.push(p);
- }
- var url = "http://api.netflix.com/users/" + signer.userId + "/ratings/title"
- + "?title_refs=" + refs.join(",");
-
- var signedArgs = dojox.io.OAuth.sign("GET", {
- url: url,
- handleAs: "xml",
- load: function(xml, ioArgs){
- // parse the info, merge and save
- var node, items = xml.evaluate("//ratings/ratings_item", xml), a = [], toSave = [];
- while(node = items.iterateNext()){
- var tmp = ps.titles.fromXml(node),
- r = tmp.ratings,
- title = titles[tmp.guid];
- if(!title || dojo.isString(title)){
- // for some reason we only have a string.
- title = tmp;
- } else {
- if(r.predicted !== null){ title.ratings.predicted = r.predicted; }
- if(r.user !== null){ title.ratings.user = r.user; }
- }
- a.push(title);
- qd.services.item(title);
- toSave.push(title);
- ratings.push(title);
- }
- args.result.call(args, a, ioArgs);
- dojo.forEach(toSave, function(item){
- saveTitle(item);
- });
- },
- error: function(err, ioArgs){
- console.warn("Batch ratings fetch: ", err);
- }
- }, signer);
- return dojo.xhrGet(signedArgs);
- }
- function encode(s){
- if(!s){ return ""; }
- return encodeURIComponent(s)
- .replace(/\!/g, "%2521")
- .replace(/\*/g, "%252A")
- .replace(/\'/g, "%2527")
- .replace(/\(/g, "%2528")
- .replace(/\)/g, "%2529");
- }
- dojo.mixin(qd.services.online.titles, {
- // summary:
- // The online-based service to get any title information, including
- // ratings and recommendations.
- save: function(/* Object */item){
- // summary:
- // Save the passed title to the database.
- saveTitle(item);
- },
- clear: function(){
- // summary:
- // Clear all titles out of the database.
- db.execute({
- sql: "DELETE FROM Title",
- result: function(data){
- // don't do anything for now.
- }
- });
- },
- /*=====
- qd.services.online.titles.find.__FindArgs = function(term, start, max, result, error){
- // summary:
- // Arguments object for doing a title search
- // term: String
- // The partial title to be looking for.
- // start: Number?
- // The page index to start on. Default is 0.
- // max: Number?
- // The maximum number of results to find. Default is 25 (supplied by Netflix).
- // result: Function?
- // The callback function that will be executed when a result is
- // fetched.
- // error: Function?
- // The callback function to be executed if there is an error in fetching.
- }
- =====*/
- find: function(/* qd.services.online.titles.find.__FindArgs */kwArgs){
- // summary:
- // Use the Netflix API directly, and try to cache any results as they come.
- var dfd = util.prepare(kwArgs),
- signer = qd.app.authorization;
- var t = encodeURIComponent(util.clean(kwArgs.term));
- if(t.match(/!|\*|\'|\(|\)/g)){
- t = encode(t);
- }
- dojo.xhrGet(dojox.io.OAuth.sign("GET", {
- url: "http://api.netflix.com/catalog/titles?"
- + "term=" + t
- + (kwArgs.start ? "&start_in…
Large files files are truncated, but you can click here to view the full file