1 /*
2 * ZoteroBetterBibTeX.cpp
3 *
4 * Copyright (C) 2009-20 by RStudio, Inc.
5 *
6 * Unless you have received this program directly from RStudio pursuant
7 * to the terms of a commercial license agreement with RStudio, then
8 * this program is licensed to you under the terms of version 3 of the
9 * GNU Affero General Public License. This program is distributed WITHOUT
10 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13 *
14 */
15
16 #include "ZoteroBetterBibTeX.hpp"
17
18 #include <shared_core/json/Json.hpp>
19 #include <shared_core/json/Json.hpp>
20
21 #include <core/http/TcpIpBlockingClient.hpp>
22 #include <core/http/SocketUtils.hpp>
23
24 #include <session/prefs/UserPrefs.hpp>
25 #include <session/prefs/UserState.hpp>
26
27 #include <session/SessionModuleContext.hpp>
28
29 #include "ZoteroCollections.hpp"
30 #include "ZoteroCollectionsLocal.hpp"
31
32 namespace rstudio {
33
34 using namespace core;
35
36 namespace session {
37 namespace modules {
38 namespace zotero {
39
40 using namespace collections;
41
42 namespace {
43
44 template<typename T>
betterBibtexJsonRpcRequest(const std::string & method,const json::Array & params,T * pResponse,std::string * pWarning)45 bool betterBibtexJsonRpcRequest(const std::string& method, const json::Array& params, T* pResponse, std::string* pWarning)
46 {
47 // build request
48 http::Request request;
49 request.setMethod("POST");
50 request.setContentType("application/json");
51 request.setHeader("Accept", "application/json");
52 request.setUri("/better-bibtex/json-rpc");
53 json::Object rpcRequest;
54 rpcRequest["jsonrpc"] = "2.0";
55 rpcRequest["method"] = method;
56 rpcRequest["params"] = params;
57 request.setBody(rpcRequest.writeFormatted());
58
59 http::Response response;
60 Error error = http::sendRequest("localhost", "23119", boost::posix_time::milliseconds(10000), request, &response);
61 if (!error)
62 {
63 if (response.statusCode() == http::status::Ok)
64 {
65 json::Value responseValue;
66 Error error = responseValue.parse(response.body());
67 if (!error)
68 {
69 if (responseValue.isObject() && responseValue.getObject().hasMember("result"))
70 {
71 json::Value resultValue = responseValue.getObject()["result"];
72 if (json::isType<T>(resultValue))
73 {
74 *pResponse = resultValue.getValue<T>();
75 return true;
76 }
77 }
78
79 }
80
81 *pWarning = "Unexpected data format provided by Better BibTeX";
82 LOG_ERROR_MESSAGE(*pWarning + " : " + response.body());
83
84 }
85 else
86 {
87 *pWarning = "Unexpected status " +
88 safe_convert::numberToString(response.statusCode()) + " from Better BibTeX";
89 LOG_ERROR_MESSAGE(*pWarning);
90 }
91
92 }
93 else if (http::isConnectionUnavailableError(error) ||
94 (error = systemError(boost::system::errc::timed_out, ErrorLocation())))
95 {
96 *pWarning = "Unable to connect to Better BibTeX. Please ensure that Zotero is running.";
97 }
98 else
99 {
100 *pWarning = "Unexpected error communicating with Better BibTex";
101 LOG_ERROR(error);
102 }
103
104 return false;
105 }
106
107
108 } // anonymous namespace
109
110
betterBibtexInConfig(const std::string & config)111 bool betterBibtexInConfig(const std::string& config)
112 {
113 return config.find_first_of("extensions.zotero.translators.better-bibtex") != std::string::npos;
114 }
115
betterBibtexEnabled()116 bool betterBibtexEnabled()
117 {
118 return session::prefs::userState().zoteroUseBetterBibtex();
119 }
120
betterBibtexProvideIds(const collections::ZoteroCollections & collections,collections::ZoteroCollectionsHandler handler)121 void betterBibtexProvideIds(const collections::ZoteroCollections& collections,
122 collections::ZoteroCollectionsHandler handler)
123 {
124 // get zotero key for each item in all of the collections
125 std::vector<std::string> zoteroKeys;
126 for (auto collection : collections)
127 {
128 std::transform(collection.items.begin(), collection.items.end(), std::back_inserter(zoteroKeys), [](const json::Value& itemJson) {
129 return itemJson.getObject()["key"].getString();
130 });
131 }
132
133 // call better bibtex to create a map of zotero keys to bbt citation ids
134 std::string warning;
135 std::map<std::string,std::string> keyMap;
136 json::Object keyMapJson;
137 json::Array params;
138 params.push_back(json::toJsonArray(zoteroKeys));
139 if (betterBibtexJsonRpcRequest("item.citationkey", params, &keyMapJson, &warning))
140 {
141 for (auto member : keyMapJson)
142 {
143 if (member.getValue().isString())
144 keyMap[member.getName()] = member.getValue().getString();
145 }
146 }
147
148 // new set of collections with updated ids
149 collections::ZoteroCollections updatedCollections;
150 std::transform(collections.begin(), collections.end(), std::back_inserter(updatedCollections),
151 [&keyMap](const collections::ZoteroCollection& collection) {
152 json::Array updatedItems;
153 std::transform(collection.items.begin(), collection.items.end(), std::back_inserter(updatedItems),
154 [&keyMap](const json::Value& itemJson) {
155 json::Object itemObject = itemJson.getObject();
156 if (itemObject.hasMember("key"))
157 {
158 std::string zoteroKey = itemObject["key"].getString();
159 std::map<std::string,std::string>::const_iterator it = keyMap.find(zoteroKey);
160 if (it != keyMap.end())
161 {
162 itemObject["id"] = it->second;
163 }
164 }
165 return itemObject;
166 });
167 collections::ZoteroCollection updatedCollection(collections::ZoteroCollectionSpec(collection.name, collection.key, collection.parentKey, collection.version));
168 updatedCollection.items = updatedItems;
169 return updatedCollection;
170 });
171
172 // return
173 handler(Success(), updatedCollections, warning);
174 }
175
setBetterBibtexNotFoundResult(const std::string & warning,json::JsonRpcResponse * pResponse)176 void setBetterBibtexNotFoundResult(const std::string& warning, json::JsonRpcResponse* pResponse)
177 {
178 json::Object resultJson;
179 resultJson["status"] = "nohost";
180 resultJson["warning"] = warning;
181 pResponse->setResult(resultJson);
182 }
183
betterBibtexExport(const json::JsonRpcRequest & request,json::JsonRpcResponse * pResponse)184 Error betterBibtexExport(const json::JsonRpcRequest& request,
185 json::JsonRpcResponse* pResponse)
186 {
187 // extract params
188 json::Array itemKeysJson;
189 std::string translatorId;
190 int libraryId;
191 Error error = json::readParams(request.params, &itemKeysJson, &translatorId, &libraryId);
192 if (error)
193 return error;
194
195 // include library in item keys
196 boost::format fmt("%1%:%2%");
197 for (std::size_t i=0; i<itemKeysJson.getSize(); i++)
198 itemKeysJson[i] = boost::str(fmt % libraryId % itemKeysJson[i].getString());
199
200 // get citation keys
201 std::string warning;
202 json::Object keyMapJson;
203 json::Array params;
204 params.push_back(itemKeysJson);
205 if (betterBibtexJsonRpcRequest("item.citationkey", params, &keyMapJson, &warning))
206 {
207 // extract keys
208 json::Array citekeysJson;
209 std::transform(keyMapJson.begin(), keyMapJson.end(), std::back_inserter(citekeysJson),
210 [](const json::Object::Member& member) {
211 return member.getValue();
212 });
213
214 // perform export
215 params.clear();
216 params.push_back(citekeysJson);
217 params.push_back(translatorId);
218 params.push_back(libraryId);
219 json::Array exportJson;
220 if (betterBibtexJsonRpcRequest("item.export", params, &exportJson, &warning))
221 {
222 if (exportJson.getSize() >= 3 &&
223 exportJson[0].isInt() && exportJson[0].getInt() == 200 &&
224 exportJson[2].isString())
225 {
226 json::Object jsonResult;
227 jsonResult["status"] = "ok";
228 jsonResult["message"] = exportJson[2].getString();
229 pResponse->setResult(jsonResult);
230 }
231 else
232 {
233 std::string warning = "Unexpected response from Better BibTeX";
234 LOG_ERROR_MESSAGE(warning + " : " + exportJson.write());
235 setBetterBibtexNotFoundResult(warning, pResponse);
236 }
237 }
238 else
239 {
240 setBetterBibtexNotFoundResult(warning, pResponse);
241 }
242 }
243 else
244 {
245 setBetterBibtexNotFoundResult(warning, pResponse);
246 }
247
248 return Success();
249 }
250
betterBibtexInit()251 Error betterBibtexInit()
252 {
253 // force better bibtex pref off if the config isn't found
254 if (collections::localZoteroAvailable())
255 {
256 collections::DetectedLocalZoteroConfig config = collections::detectedLocalZoteroConfig();
257 if (prefs::userState().zoteroUseBetterBibtex() && !config.betterBibtex)
258 prefs::userState().setZoteroUseBetterBibtex(false);
259 }
260
261 return Success();
262 }
263
264
265 } // end namespace zotero
266 } // end namespace modules
267 } // end namespace session
268 } // end namespace rstudio
269