Skip to content

Instantly share code, notes, and snippets.

@xaiki
Created October 17, 2016 15:54
Show Gist options
  • Save xaiki/72cbd9442e4138c698bfdb990a85db8c to your computer and use it in GitHub Desktop.
Save xaiki/72cbd9442e4138c698bfdb990a85db8c to your computer and use it in GitHub Desktop.
(function (App) {
'use strict';
var _ = require('underscore'),
Datastore = require('nedb'),
TTL = 1000 * 60 * 60 * 24;
var startupTime = window.performance.now();
console.info('Database path: ' + data_path);
process.env.TZ = 'America/New_York'; // set same api tz
function promisifyDatastore(datastore) {
datastore.insert = Q.denodeify(datastore.insert, datastore);
datastore.update = Q.denodeify(datastore.update, datastore);
datastore.remove = Q.denodeify(datastore.remove, datastore);
}
var pluckToHash = function (items, key) {
return _.pluck(items, key)
.reduce(function (o, v) {
o[v] = 1
}, {});
}
var extractIds = function (items) {
return pluckToHash ('imdb_id')
};
var extractMovieIds = function (items) {
return pluckToHash ('movie_id')
};
// This utilizes the exec function on nedb to turn function calls into promises
var promisifyDb = function (obj) {
return Q.Promise(function (resolve, reject) {
obj.exec(function (error, result) {
if (error) {
return reject(error);
} else {
return resolve(result);
}
});
});
};
var Database = Backbone.Model.extend({
defaults: {
id: 'imdb_id',
name: null
},
initialize: function(attrs) {
console.error('this', this, attrs)
var name = this.get('name');
console.error (name, 'initialize');
this.db = new Datastore({
filename: path.join(data_path, 'data/'+ name +'.db'),
autoload: true
});
promisifyDatastore(this.db);
},
/* XXX(xaiki): need to use apply */
dbCall: function(fn) {
var self = this
var args = Array.prototype.slice.call(arguments, 1)
var promise = promisifyDb(this.db[fn].apply(args))
promise.then(function (ret) {
self.emit(fn, ret)
})
return promise
},
selectorFromId: function (id) {
var selector = {}
selector[this.id] = id
return selector
},
selectorFromData: function (data) {
return this.selectorFromId(data[this.id])
},
add: function (data, extra) {
var formatedData = this.formatData(data, extra)
return this.dbCall('insert', formatedData)
},
update: function (data, extra) {
var formatedData = this.formatData(data, extra)
var selector = this.selectorFromData(formatedData)
return this.dbCall('update', selector, formatedData);
},
remove: function (id) {
var selector = this.selectorFromId(id)
return this.dbCall('remove', selector)
},
reset: function () {
return this.dbCall('remove', {}, {multi: true})
},
write: function (data, extra) {
var formatedData = this.formatData(data, extra)
var selector = this.selectorFromData(formatedData)
var db = this.db
return this.find(selector)
.then(function (result) {
if (result) {
// XXX(xaiki): prune id for $set
return db.update(selector, {
$set: data
}, {});
} else {
return db.insert(data);
}
})
},
find: function (id) {
if (!id)
return this.dbCall('find', {})
var selector = this.selectorFromId(id)
return this.dbCall('findOne', selector)
},
formatData: function (data, extra) { return data }
})
/* this is a database where elements can be 'watched' */
var ContentDatabase = Database.extend({
defaults: {
watchedId: null,
watchedType: null
},
checkWatched: function (data) {
this.getWatched(data)
.then(function (data) {
return (data !== null && data.length > 0);
});
},
getWatched: function (data) {
var selector = {}
if (data) {
selector[this.watchedId] = data[this.watchedId]
} else {
selector['type'] = this.watchedType
}
return promisifyDb(db.watched.find(selector))
},
markWatched: function (data) {
if (data instanceof Array) {
return data.map(this.markWatched);
}
var id = data[this.id]
if (!id) {
console.error('This shouldn\'t be called');
return Q();
}
// XXX(xaiki): this is disgusting
App[this.watchedObject][id] = 1;
var watchedData = this.formatWatched(data)
watchedData['date'] = new Date();
return promisifyDb(db.watched.insert(watchedData))
.then (this.onWatched)
},
markNotWatched: function (data) {
if (data instanceof Array) {
return data.map(this.markNotWatched);
}
var id = data[this.id]
if (!id) {
console.error('This shouldn\'t be called');
return Q();
}
// XXX(xaiki): this is disgusting
delete(App[this.watchedObject][id])
var watchedData = this.formatWatched(data)
return promisifyDb(db.watched.remove(watchedData))
.then(this.onUnWatched)
},
formatWatched: function(data) {return data},
onWatched: function() {},
onUnWatched: function() {},
})
var MoviesDatabase = ContentDatabase.extend({
defaults: {
id: 'imdb_id',
name: 'movies',
watchedId: 'imdb_id',
watchedObject: 'watchedMovies'
},
initialize: function (attrs) {
ContentDatabase.prototype.initialize.call(this, attrs)
App.vent.on('movie:watched', _.bind(this.markWatched, this));
App.vent.on('movie:unwatched', _.bind(this.markNotWatched, this));
this.db.ensureIndex({
fieldName: 'imdb_id',
unique: true
});
this.db.removeIndex('imdb_id');
this.db.removeIndex('tmdb_id');
},
formatWatched: function (data) {
return {
movie_id: data.imdb_id.toString(),
type: 'movie'
}
}
})
var ShowsDatabase = ContentDatabase.extend({
defaults: {
id: 'imdb_id',
name: 'shows',
watchedId: 'tvdb_id',
watchedObject: 'watchedShows',
watchedType: 'episode'
},
initialize: function (attrs) {
ContentDatabase.prototype.initialize.call(this, attrs)
// Create unique indexes for the various id's for shows and movies
this.db.ensureIndex({
fieldName: 'imdb_id',
unique: true
});
this.db.ensureIndex({
fieldName: 'tvdb_id',
unique: true
});
App.vent.on('show:watched', _.bind(this.markWatched, this));
App.vent.on('show:unwatched', _.bind(this.markNotWatched, this));
},
formatWatched: function (data) {
return {
tvdb_id: data.tvdb_id.toString(),
imdb_id: data.imdb_id.toString(),
season: data.season.toString(),
episode: data.episode.toString(),
type: 'episode',
date: new Date()
}
},
onWatched: function (data) {
App.vent.trigger('show:watched:' + data.tvdb_id, data);
},
onUnWatched: function (data) {
App.vent.trigger('show:unwatched:' + data.tvdb_id, data);
}
})
var SettingsDatabase = Database.extend({
defaults: {
id: 'key',
name: 'settings'
},
initialize: function (attrs) {
Database.prototype.initialize.call(this, attrs)
// settings key uniqueness
this.db.ensureIndex({
fieldName: 'key',
unique: true
});
}
})
var BookmarksDatabase = Database.extend({
defaults: {
id: 'imdb_id',
name: 'bookmarks'
},
initialize: function(attrs) {
Database.prototype.initialize.call(this, attrs)
this.on('add', function(id) {App.userBookmarks[id] = 1})
this.on('remove', function(id) {delete(App.userBookmarks[id])})
this.db.ensureIndex({
fieldName: 'imdb_id',
unique: true
});
},
formatData: function (id, type) {
return {
imdb_id: id,
type: type
}
},
find: function (data) {
if (data)
return this.findOne(data)
return this.findAll()
},
findAll: function () {
return promisifyDb(db.bookmarks.find({}))
.then(function (data) {
var bookmarks = [];
if (data) {
bookmarks = extractIds(data);
}
return bookmarks;
});
},
findOne: function (data) {
var page = data.page - 1;
var byPage = 50;
var offset = page * byPage;
var query = {};
if (data.type) {
query.type = data.type;
}
return promisifyDb(db[this.name].find(query).skip(offset).limit(byPage));
}
})
App.DatabaseHelpers = {
getUserInfo: function () {
var bookmarks = BookmarksDatabase.get()
.then(function (data) {
App.userBookmarks = data;
});
var movies = MoviesDatabase.getWatched()
.then(function (data) {
App.watchedMovies = extractMovieIds(data);
});
var episodes = ShowsDatabase.getWatched()
.then(function (data) {
App.watchedShows = extractIds(data)
});
return Q.all([bookmarks, movies, episodes]);
},
deleteDatabases: function () {
Databases.keys().map(function (name) {
fs.unlinkSync(path.join(data_path, 'data/'+name+'.db'));
})
return Q.Promise(function (resolve, reject) {
var req = indexedDB.deleteDatabase(App.Config.cache.name);
req.onsuccess = function () {
resolve();
};
req.onerror = function () {
resolve();
};
});
},
initialize: function () {
// we'll intiatlize our settings and our API SSL Validation
// we build our settings array
return App.bootstrapPromise
.then(App.DatabaseHelpers.getUserInfo)
.then(App.Databases.settings.find)
.then(function (data) {
if (data !== null) {
for (var key in data) {
Settings[data[key].key] = data[key].value;
}
} else {
win.warn('is it possible to get here');
}
// new install?
if (Settings.version === false) {
window.__isNewInstall = true;
}
App.vent.trigger('initHttpApi');
/*return AdvSettings.checkApiEndpoints([
Settings.updateEndpoint
]);*/
})
.then(function () {
// set app language
window.setLanguage(Settings.language);
// set hardware settings and usefull stuff
return AdvSettings.setup();
})
.then(function () {
App.Trakt = App.Config.getProviderForType('metadata');
App.TVShowTime = App.Config.getProviderForType('tvst');
App.TVShowTime.restoreToken();
// check update
var updater = new App.Updater();
updater.update()
.catch(function (err) {
win.error('updater.update()', err);
});
})
.catch(function (err) {
win.error('Error starting up', err);
});
}
};
var WatchedDatabase = new Database({id: 'key', name: 'watched'})
App.Databases = [
new BookmarksDatabase(),
new SettingsDatabase(),
new ShowsDatabase(),
new MoviesDatabase(),
WatchedDatabase
].reduce(function (o, v) {
o[v.get('name')] = v;
return o;
}, {})
})(window.App);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment