Abstract out cache mechanisms, move search elements to their own component, disable locks in development mode, fix database cleanup in Electron renderer processes, add version display for search results

master
Sven Slootweg 8 years ago
parent a94e22b926
commit 63e7c883f3

@ -20,6 +20,7 @@
"default-value": "0.0.3",
"document-offset": "^1.0.4",
"document-ready-promise": "^3.0.1",
"dotty": "0.0.2",
"electron-prebuilt": "^1.0.1",
"file-url": "^1.1.0",
"in-array": "^0.1.2",

@ -52,6 +52,10 @@ bar.on("ready", () => {
//bar.showWindow();
});
bar.on("request-quit", () => {
app.quit();
});
ipcMain.on("resize", (event, newSize) => {
if (newSize.width != null) {
bar.setOption("width", newSize.width);
@ -74,21 +78,5 @@ Promise.try(() => {
}).then(() => {
return rfr("lib/db/json-db")(path.join(appDirectories.userData(), "db.json"));
}).then((db) => {
let metadataCache = db.collection("metadataCache");
const getPackageMetadata = rfr("lib/package/fetch-metadata")({
CacheError: errors.CacheError,
get: function(packageName) {
try {
return metadataCache.findOne({name: packageName});
} catch (err) { // FIXME
throw new errors.CacheError("Not in cache");
}
},
set: function(packageName, metadata) {
return metadataCache.upsertBy("name", metadata);
}
});
});

@ -2,57 +2,9 @@ app
.logo-section
img(src="../../assets/npm.svg", height=40)
.bar-section
search-box
search-results(results="{results}")
search
.window-height-marker
script.
const Promise = require("bluebird");
const {ipcRenderer} = require("electron");
const rfr = require("rfr");
const search = rfr("lib/search/constructor-io")("CD06z4gVeqSXRiDL2ZNK");
this.mixin(require("riot-query").mixin);
let lastQuery;
this.results = []
this.on("mount", () => {
let searchBox = this.queryOne("search-box");
let searchResults = this.queryOne("search-results");
searchBox.on("selectionUp", () => {
searchResults.moveSelectionUp();
});
searchBox.on("selectionDown", () => {
searchResults.moveSelectionDown();
});
searchBox.on("queryChanged", (query) => {
Promise.try(() => {
lastQuery = query;
return search(query);
}).then((results) => {
/* Sometimes, search results may come back out of order. Here we check whether
* the lastQuery is still the same as when we initially made the request. */
if (lastQuery === query) {
this.results = results.packages;
this.update();
}
});
});
searchBox.on("cancel", () => {
ipcRenderer.send("closeSearch");
})
});
this.on("updated", () => {
global.triggerWindowResize();
});
style(scoped, type="scss").
.logo-section {
position: absolute;

@ -1,6 +1,5 @@
'use strict';
require("../search-box");
require("../search-results");
require("../search");
require("./component");

@ -1,7 +1,8 @@
search-results
.result(each="{result, i in opts.results}", class="{active: i === currentSelection}")
h2 {result.name}
.description {result.description}
.version v{result.latestVersion || "..."}
.description {result.description || "(no description)"}
script.
let lastResults;
@ -54,4 +55,10 @@ search-results
.description {
font-size: 13px;
}
.version {
font-style: italic;
float: right;
font-size: 13px;
}
}

@ -0,0 +1,83 @@
search
search-box
search-results(results="{results}")
script.
const Promise = require("bluebird");
const path = require("path");
const dotty = require("dotty");
const {ipcRenderer} = require("electron");
const rfr = require("rfr");
const appDirectory = require("appdirectory");
let appDirectories = new appDirectory("npmbar");
const jsonDB = rfr("lib/db/json-db");
const jsonDBCache = rfr("lib/db/json-db-cache");
this.mixin(require("riot-query").mixin);
Object.assign(this, {
results: []
});
let lastQuery;
this.on("updated", () => {
global.triggerWindowResize();
});
this.on("mount", () => {
Promise.try(() => {
return Promise.all([
jsonDB(path.join(appDirectories.userData(), "search-cache.json")),
jsonDB(path.join(appDirectories.userData(), "package-cache.json"))
]);
}).spread((searchCacheDB, packageCacheDB) => {
let searchCache = jsonDBCache(searchCacheDB, "searchCache");
let packageCache = jsonDBCache(packageCacheDB, "packageCache");
const search = rfr("lib/search/constructor-io")("CD06z4gVeqSXRiDL2ZNK", searchCache);
const lookupPackage = rfr("lib/package/fetch-metadata")(packageCache);
let searchBox = this.queryOne("search-box");
let searchResults = this.queryOne("search-results");
searchBox.on("selectionUp", () => {
searchResults.moveSelectionUp();
});
searchBox.on("selectionDown", () => {
searchResults.moveSelectionDown();
});
searchBox.on("queryChanged", (query) => {
Promise.try(() => {
lastQuery = query;
return search(query);
}).then((results) => {
/* Sometimes, search results may come back out of order. Here we check whether
* the lastQuery is still the same as when we initially made the request. */
if (lastQuery === query) {
this.results = results.packages;
this.update();
this.results.forEach((result, i) => {
Promise.try(() => {
return lookupPackage(result.name);
}).then((metadata) => {
if (dotty.exists(metadata, "dist-tags.latest")) {
result.latestVersion = metadata["dist-tags"].latest;
this.update();
}
})
});
}
});
});
searchBox.on("cancel", () => {
ipcRenderer.send("closeSearch");
})
});
});

@ -0,0 +1,6 @@
'use strict';
require("../search-box");
require("../search-results");
require("./component");

@ -0,0 +1,38 @@
'use strict';
const Promise = require("bluebird");
/* NOTE: This assumes that the first argument to the cacheized call, will be
* the cache key. */
module.exports = function(cache, cb) {
function getFromRemote(...args) {
return Promise.try(() => {
return cb(...args);
}).tap((result) => {
cache.set(args[0], Object.assign({
$cacheKey: args[0]
}, result));
});
}
function getFromCache(...args) {
return Promise.try(() => {
return cache.get(args[0]);
}).then((metadata) => {
if (metadata._cacheExpiry < Date.now()) {
throw new cache.CacheError("Cached data expired");
} else {
return metadata;
}
});
}
return function(...args) {
return Promise.try(() => {
return getFromCache(...args);
}).catch(cache.CacheError, (err) => {
return getFromRemote(...args);
});
}
}

@ -0,0 +1,22 @@
'use strict';
const rfr = require("rfr");
const errors = rfr("lib/util/errors");
module.exports = function(db, collectionName) {
let cacheCollection = db.collection(collectionName);
return {
CacheError: errors.CacheError,
get: function(packageName) {
try {
return cacheCollection.findOne({$cacheKey: packageName});
} catch (err) { // FIXME
throw new errors.CacheError("Not in cache");
}
},
set: function(packageName, metadata) {
return cacheCollection.upsertBy("$cacheKey", metadata);
}
}
}

@ -25,21 +25,28 @@ const rfr = require("rfr");
const promiseDebounce = rfr("lib/util/promise-debounce");
const escapeCollectionName = rfr("lib/db/escape-collection-name"); // FIXME: Actually split this up into collections...
/* We don't use locks in development mode, as various build tools won't play nice with this. */
let developmentMode = (process.env.NODE_ENV === "development");
function getLockPath(path) {
return `${path}.lock`;
}
function obtainLock(path) {
return Promise.try(() => {
return fs.writeFileAsync(getLockPath(path), "", {
flag: "wx"
});
if (!developmentMode) {
return fs.writeFileAsync(getLockPath(path), "", {
flag: "wx"
});
}
});
}
function releaseLock(path) {
return Promise.try(() => {
return fs.unlinkAsync(getLockPath(path));
if (!developmentMode) {
return fs.unlinkAsync(getLockPath(path));
}
});
}
@ -160,12 +167,15 @@ module.exports = function(path) {
let db = {
opened: true,
closing: false,
close: function() {
return Promise.try(() => {
this.closing = true;
return queueWrite();
}).then(() => {
return releaseLock(path);
}).then(() => {
this.closing = false;
this.opened = false;
});
},
@ -181,20 +191,48 @@ module.exports = function(path) {
function cleanup() {
console.log("Doing cleanup...");
if (db.opened === true) {
db.close();
return db.close();
}
}
if (process.versions.electron != null) {
/* Running in Electron */
require("electron").app.on("will-quit", () => {
cleanup();
})
} else {
/* Running in a different Node-y environment */
process.on("beforeExit", () => {
cleanup();
});
if (!developmentMode) {
if (process.versions.electron != null) {
/* Running in Electron */
let {app, ipcRenderer} = require("electron");
if (app != null) {
/* Main process */
app.on("will-quit", () => {
cleanup();
});
} else if (ipcRenderer != null) {
/* Renderer process */
window.addEventListener("beforeunload", (event) => {
if (db.opened === true) {
if (db.closing === false) {
Promise.try(() => {
return cleanup();
}).then(() => {
ipcRenderer.send("request-quit");
});
}
return false;
}
});
ipcRenderer.on("will-quit", () => {
cleanup();
})
} else {
// ???
}
} else {
/* Running in a different Node-y environment */
process.on("beforeExit", () => {
cleanup();
});
}
}
return db;

@ -2,9 +2,12 @@
const Promise = require("bluebird");
const bhttp = require("bhttp");
const rfr = require("rfr");
const cacheize = rfr("lib/db/cacheize");
module.exports = function(cache) {
function getFromRemote(packageName) {
return cacheize(cache, function(packageName) {
return Promise.try(() => {
let encodedPackageName = encodeURIComponent(packageName).replace(/%40/g, "@");
return bhttp.get(`https://registry.npmjs.org/${encodedPackageName}`);
@ -15,28 +18,6 @@ module.exports = function(cache) {
} else {
return response.body;
}
}).tap((metadata) => {
cache.set(packageName, metadata);
});
}
function getFromCache(packageName) {
return Promise.try(() => {
return cache.get(packageName);
}).then((metadata) => {
if (metadata._cacheExpiry < Date.now()) {
throw new cache.CacheError("Cached data expired");
} else {
return metadata;
}
});
}
return function(packageName) {
return Promise.try(() => {
return getFromCache(packageName);
}).catch(cache.CacheError, (err) => {
return getFromRemote(packageName);
});
}
});
}

@ -4,8 +4,11 @@ const Promise = require("bluebird");
const bhttp = require("bhttp");
const querystring = require("querystring");
const defaultValue = require("default-value");
const rfr = require("rfr");
module.exports = function(autocompleteKey) {
const cacheize = rfr("lib/db/cacheize");
module.exports = function(autocompleteKey, cache) {
function createQueryString(query, options) {
return querystring.stringify({
autocomplete_key: autocompleteKey,
@ -19,14 +22,13 @@ module.exports = function(autocompleteKey) {
return `https://ac.cnstrc.com/autocomplete/${encodeURIComponent(query)}?${createQueryString(query, options)}`;
}
return function doSearch(query, options = {}) {
return cacheize(cache, function doSearch(query, options = {}) {
return Promise.try(() => {
if (query.trim() !== "") {
return Promise.try(() => {
return bhttp.get(createUrl(query, options));
}).then((response) => {
if (response.statusCode === 200) {
console.log(response.body);
return {
packages: response.body.sections.packages.map((pkg) => {
return {
@ -47,6 +49,5 @@ module.exports = function(autocompleteKey) {
}
}
})
}
})
}
Loading…
Cancel
Save