diff --git a/chrome/content/zotero/xpcom/localAPI/server_localAPI.js b/chrome/content/zotero/xpcom/localAPI/server_localAPI.js index a142a285db..b7581426ff 100644 --- a/chrome/content/zotero/xpcom/localAPI/server_localAPI.js +++ b/chrome/content/zotero/xpcom/localAPI/server_localAPI.js @@ -741,6 +741,71 @@ Zotero.Server.Endpoints["/api/users/:userID/items/:itemKey/file/view/url"] = Zot Zotero.Server.Endpoints["/api/groups/:groupID/items/:itemKey/file/view/url"] = Zotero.Server.LocalAPI.ItemFile; +Zotero.Server.LocalAPI.ItemFullText = class extends LocalAPIEndpoint { + supportedMethods = ['GET']; + + async run({ pathParams, libraryID }) { + let item = await Zotero.Items.getByLibraryAndKeyAsync(libraryID, pathParams.itemKey); + if (!item || !item.isFileAttachment() || !Zotero.Fulltext.isCachedMIMEType(item.attachmentContentType)) { + return _404; + } + let file = Zotero.Fulltext.getItemCacheFile(item); + if (!file.exists()) { + return _404; + } + let { indexedPages, totalPages, indexedChars, totalChars, version } = await Zotero.DB.rowQueryAsync( + "SELECT indexedPages, totalPages, indexedChars, totalChars, version FROM fulltextItems WHERE itemID=?", + item.id + ); + return [ + 200, + { + 'Content-Type': 'application/json', + 'Last-Modified-Version': version, + }, + JSON.stringify( + { + content: await Zotero.File.getContentsAsync(file), + indexedPages: indexedPages ?? undefined, + totalPages: totalPages ?? undefined, + indexedChars: indexedChars ?? undefined, + totalChars: totalChars ?? undefined, + }, + null, + 4 + ) + ]; + } +}; +Zotero.Server.Endpoints["/api/users/:userID/items/:itemKey/fulltext"] = Zotero.Server.LocalAPI.ItemFullText; +Zotero.Server.Endpoints["/api/groups/:groupID/items/:itemKey/fulltext"] = Zotero.Server.LocalAPI.ItemFullText; + + +Zotero.Server.LocalAPI.FullText = class extends LocalAPIEndpoint { + supportedMethods = ['GET']; + + async run({ searchParams, libraryID }) { + let since = parseInt(searchParams.get('since')); + if (Number.isNaN(since)) { + return [400, 'text/plain', `Invalid 'since' value '${searchParams.get('since')}'`]; + } + let rows = await Zotero.DB.queryAsync( + "SELECT I.key, FI.version " + + "FROM fulltextItems FI JOIN items I USING (itemID) " + + "WHERE libraryID=? AND FI.version>?", + [libraryID, since] + ); + let obj = {}; + for (let row of rows) { + obj[row.key] = row.version; + } + return [200, 'application/json', JSON.stringify(obj, null, 4)]; + } +}; +Zotero.Server.Endpoints["/api/users/:userID/fulltext"] = Zotero.Server.LocalAPI.FullText; +Zotero.Server.Endpoints["/api/groups/:groupID/fulltext"] = Zotero.Server.LocalAPI.FullText; + + Zotero.Server.LocalAPI.Searches = class extends LocalAPIEndpoint { supportedMethods = ['GET']; diff --git a/test/tests/server_localAPITest.js b/test/tests/server_localAPITest.js index cab89a8646..76ed587095 100644 --- a/test/tests/server_localAPITest.js +++ b/test/tests/server_localAPITest.js @@ -142,6 +142,25 @@ describe("Local API Server", function () { { responseType: 'text', followRedirects: false }); assert.isTrue(request.getResponseHeader('Location').startsWith('file:')); }); + + it("should return full-text data from /fulltext", async function () { + let { response } = await apiGet(`/users/0/items/${subcollectionAttachment.key}/fulltext`); + assert.deepEqual(response, { + content: 'Zotero [zoh-TAIR-oh] is a free, easy-to-use tool to help you collect, organize, cite, and share your research sources.', + indexedPages: 1, + totalPages: 1, + }); + }); + + it("should return a 404 from /fulltext when attachment has no full-text content", async function () { + let tempAttachment = await importFileAttachment('empty.pdf'); + let { response } = await apiGet(`/users/0/items/${tempAttachment.key}/fulltext`, { + successCodes: [404], + responseType: 'text' + }); + assert.equal(response, 'Not found'); + await tempAttachment.eraseTx(); + }); }); describe("?itemType", function () { @@ -280,7 +299,14 @@ describe("Local API Server", function () { assert.equal(response[0].meta.numItems, 1); }); }); - + + describe("/fulltext?since=", function () { + it("should return items with full-text content", async function () { + let { response } = await apiGet('/users/0/fulltext?since=0'); + assert.deepEqual(Object.entries(response), [[subcollectionAttachment.key, subcollectionAttachment.version]]); + }); + }); + describe("/groups/", function () { it("should return 404 for unknown group", async function () { let { response } = await apiGet(