Skip to content

Commit

Permalink
Move web translation back to /web and implement other /search modes
Browse files Browse the repository at this point in the history
I had consolidated URL handling into /search, but it didn't really make
sense. Both web translation and text search can return a 300, but with
different responses and different required handling. So clients should
just parse out URLs, send those to /web (now as plain text), and send
everything else to /search like with v1.

Closes #6, closes #7
  • Loading branch information
dstillman committed Jul 26, 2018
1 parent 079ad71 commit 1d944e6
Show file tree
Hide file tree
Showing 10 changed files with 735 additions and 129 deletions.
99 changes: 48 additions & 51 deletions src/searchEndpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,77 +23,74 @@
***** END LICENSE BLOCK *****
*/

const SearchSession = require('./searchSession');

// Timeout for select requests, in seconds
//const SELECT_TIMEOUT = 120;
const SELECT_TIMEOUT = 15;
const sessionsWaitingForSelection = {};
const config = require('config');
const Translate = require('./translation/translate');
const TextSearch = require('./textSearch');

var SearchEndpoint = module.exports = {
requestsSinceGC: 0,

handle: async function (ctx, next) {
ctx.assert(ctx.is('text/plain') || ctx.is('json'), 415);

setTimeout(() => {
this.gc();
});

var data = ctx.request.body;

if (!data) {
ctx.throw(400, "POST data not provided\n");
}

// If follow-up request, retrieve session and update context
var query;
var session;
if (typeof data == 'object') {
let sessionID = data.session;
if (!sessionID) {
ctx.throw(400, "'session' not provided");
}
session = sessionsWaitingForSelection[sessionID];
if (!session) {
ctx.throw(400, "Session not found");
}
delete sessionsWaitingForSelection[sessionID];
session.ctx = ctx;
session.next = next;
session.data = data;
}
else {
session = new SearchSession(ctx, next, data);
// Look for DOI, ISBN, etc.
var identifiers = Zotero.Utilities.Internal.extractIdentifiers(data);

// Use PMID only if it's the only text in the query
if (identifiers.length && identifiers[0].PMID && identifiers[0].PMID !== data.trim()) {
identifiers = [];
}

// URL
if (typeof data == 'object' || data.match(/^https?:/)) {
await session.handleURL();

// Store session if returning multiple choices
if (ctx.response.status == 300) {
sessionsWaitingForSelection[session.id] = session;
}
// Text search
if (!identifiers.length) {
await TextSearch.handle(ctx, next);
return;
}

ctx.throw(501);
this.handleIdentifier(ctx, identifiers[0]);
},


/**
* Perform garbage collection every 10 requests
*/
gc: function () {
if ((++this.requestsSinceGC) == 3) {
for (let i in sessionsWaitingForSelection) {
let session = sessionsWaitingForSelection[i];
if (session.started && Date.now() >= session.started + SELECT_TIMEOUT * 1000) {
delete sessionsWaitingForSelection[i];
}
handleIdentifier: async function (ctx, identifier) {
// Identifier
try {
var translate = new Translate.Search();
translate.setIdentifier(identifier);
let translators = await translate.getTranslators();
if (!translators.length) {
ctx.throw(501, "No translators available", { expose: true });
}
translate.setTranslator(translators);

var items = await translate.translate({
libraryID: false
});
}
catch (e) {
if (e == translate.ERROR_NO_RESULTS) {
ctx.throw(501, e, { expose: true });
}
this.requestsSinceGC = 0;

Zotero.debug(e, 1);
ctx.throw(
500,
"An error occurred during translation. "
+ "Please check translation with the Zotero client.",
{ expose: true }
);
}

// Translation can return multiple items (e.g., a parent item and notes pointing to it),
// so we have to return an array with keyed items
var newItems = [];
items.forEach(item => {
newItems.push(...Zotero.Utilities.itemToAPIJSON(item));
});

ctx.response.body = newItems;
}
};
2 changes: 2 additions & 0 deletions src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ require('./zotero');
const Debug = require('./debug');
const Translators = require('./translators');
const SearchEndpoint = require('./searchEndpoint');
const WebEndpoint = require('./webEndpoint');

const app = module.exports = new Koa();
app.use(bodyParser({ enableTypes: ['text', 'json']}));
app.use(_.post('/web', WebEndpoint.handle.bind(WebEndpoint)));
app.use(_.post('/search', SearchEndpoint.handle.bind(SearchEndpoint)));

Debug.init(1);
Expand Down
Loading

0 comments on commit 1d944e6

Please sign in to comment.