PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/strigi-0.7.7/strigiclient/lib/htmlgui/strigihtmlgui.cpp

#
C++ | 537 lines | 479 code | 13 blank | 45 comment | 125 complexity | 1d2678b550d93a0ffc57254d1c8bab84 MD5 | raw file
Possible License(s): LGPL-2.0
  1. /* This file is part of Strigi Desktop Search
  2. *
  3. * Copyright (C) 2006 Jos van den Oever <jos@vandenoever.info>
  4. *
  5. * This library is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU Library General Public
  7. * License as published by the Free Software Foundation; either
  8. * version 2 of the License, or (at your option) any later version.
  9. *
  10. * This library is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. * Library General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Library General Public License
  16. * along with this library; see the file COPYING.LIB. If not, write to
  17. * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  18. * Boston, MA 02110-1301, USA.
  19. */
  20. #include "strigihtmlgui.h"
  21. #include <strigi/socketclient.h>
  22. #include <strigi/indexreader.h>
  23. #include <strigi/query.h>
  24. #include <strigi/queryparser.h>
  25. #include <unistd.h>
  26. #include <sys/types.h>
  27. #include <dirent.h>
  28. #include <sstream>
  29. #include <fstream>
  30. #include <iostream>
  31. #include <sys/stat.h>
  32. #include <stdlib.h>
  33. #include <string.h>
  34. using namespace std;
  35. using namespace Strigi;
  36. class StrigiHtmlGui::Private {
  37. private:
  38. HtmlHelper* h;
  39. string highlightTerms(const string& t, const Query& q) const;
  40. void printSearchResult(ostream& out,
  41. const Strigi::IndexedDocument& doc, const Query& q) const;
  42. public:
  43. SocketClient strigi;
  44. Private(HtmlHelper* h);
  45. void printSearchResults(ostream& out, const ClientInterface::Hits&,
  46. const string& query) const;
  47. };
  48. StrigiHtmlGui::StrigiHtmlGui(HtmlHelper* h) :helper(h), p(new Private(helper)) {
  49. }
  50. StrigiHtmlGui::~StrigiHtmlGui() {
  51. delete p;
  52. }
  53. void
  54. StrigiHtmlGui::printHtmlHeader(ostream& out) {
  55. // FIXME: extra spaces added at the beginning as a wordaround because
  56. // KIO discards several first chars for unknown reason
  57. out << " <?xml version='1.0' encoding='utf-8'?>\n"
  58. "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN' "
  59. "'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>\n"
  60. "<html xmlns='http://www.w3.org/1999/xhtml'>"
  61. "<head><meta http-equiv='Content-Type' "
  62. "content='text/html; charset=utf-8' />"
  63. "<link rel='stylesheet' type='text/css' href='"
  64. << helper->getCssUrl()
  65. << "'/>"
  66. "<title>Strigi Desktop Search</title>"
  67. "</head><body>";
  68. }
  69. void
  70. StrigiHtmlGui::printHeader(ostream& out, const string& path,
  71. const map<string, string> &params) {
  72. printHtmlHeader(out);
  73. out << "<div class='header'>";
  74. printMenu(out, path, params);
  75. out << "<div class='title'>Strigi Desktop Search</div>";
  76. out << "</div><div class='box'>";
  77. }
  78. void
  79. StrigiHtmlGui::printFooter(ostream& out, const string& path,
  80. const map<string, string> &params) {
  81. out << "</div>";
  82. out << "</body></html>";
  83. }
  84. void
  85. StrigiHtmlGui::printHelp(ostream& out, const string& path,
  86. const map<string, string> &params) {
  87. out << "For help see the <a href='http://strigi.sf.net'>Strigi Wiki</a>";
  88. }
  89. void
  90. StrigiHtmlGui::printAbout(ostream& out, const string& path,
  91. const map<string, string> &params) {
  92. out << "Strigi v"STRIGI_VERSION_STRING"<br/>";
  93. out << "For more details see <a href='http://strigi.sf.net/'>"
  94. "the Strigi Website</a>.";
  95. }
  96. void
  97. StrigiHtmlGui::printConfig(ostream& out, const string& path,
  98. const map<string, string> &params) {
  99. printIndexedDirs(out, path, params);
  100. }
  101. bool
  102. exists(const char* file) {
  103. struct stat s;
  104. if (stat(file, &s)) {
  105. return false;
  106. }
  107. return S_ISREG(s.st_mode);
  108. }
  109. void
  110. startDaemon() {
  111. char daemon[13];
  112. strcpy(daemon, "strigidaemon");
  113. // find the executable
  114. // get the PATH environment
  115. const char* path = getenv("PATH");
  116. const char* end = strchr(path, ':');
  117. string p;
  118. while (end) {
  119. p.assign(path, end);
  120. p.append("/");
  121. p.append(daemon);
  122. path = end+1;
  123. end = strchr(path, ':');
  124. if (exists(p.c_str())) {
  125. if (fork()) {
  126. const char * const args[] = {daemon, "clucene", 0};
  127. execvp(p.c_str(), (char **)args);
  128. }
  129. break;
  130. }
  131. }
  132. }
  133. void
  134. StrigiHtmlGui::printStatus(ostream& out, const string& path,
  135. const map<string, string> &params) {
  136. map<string, string> status;
  137. if (path == "status/start") {
  138. status = p->strigi.getStatus();
  139. if (status.size() == 0) {
  140. startDaemon();
  141. int n = 0;
  142. while (n < 5 && status.size() == 0) {
  143. sleep(1);
  144. status = p->strigi.getStatus();
  145. n++;
  146. }
  147. }
  148. } else if (path == "status/stop") {
  149. p->strigi.stopDaemon();
  150. } else if (path == "status/stopindexing") {
  151. p->strigi.stopIndexing();
  152. status = p->strigi.getStatus();
  153. } else if (path == "status/startindexing") {
  154. p->strigi.startIndexing();
  155. status = p->strigi.getStatus();
  156. } else {
  157. status = p->strigi.getStatus();
  158. }
  159. if (status.size() == 0) {
  160. out << "<p><a href='/status/start'>Start daemon</a></p>";
  161. } else {
  162. map<string, string>::const_iterator i;
  163. out << "<table>";
  164. for (i = status.begin(); i != status.end(); ++i) {
  165. out << "<tr><td>" << i->first << "</td><td>" << i->second
  166. << "</td><tr>";
  167. }
  168. out << "</table>";
  169. out << "<p><a href='/status/stop'>Stop daemon</a></p>";
  170. if (status["Status"] == "indexing") {
  171. out << "<p><a href='/status/stopindexing'>Stop indexing</a></p>";
  172. } else {
  173. out << "<p><a href='/status/startindexing'>Start indexing</a></p>";
  174. }
  175. }
  176. // automatically reload the status page
  177. out << "<script type='text/javascript'>\n"
  178. "setTimeout('window.location.replace(\"/status\")', 2000);\n"
  179. "</script>\n";
  180. }
  181. void
  182. StrigiHtmlGui::printSearch(ostream& out, const string& path,
  183. const map<string, string> &params) {
  184. string query;
  185. map<string, string>::const_iterator i = params.find("q");
  186. if (i != params.end()) query = i->second;
  187. int max = 10;
  188. i = params.find("m");
  189. if (i != params.end()) {
  190. max = atoi(i->second.c_str());
  191. }
  192. if (max <= 0 || max > 1000) max = 10;
  193. int off = 0;
  194. i = params.find("o");
  195. if (i != params.end()) {
  196. off = atoi(i->second.c_str());
  197. }
  198. if (off < 0) {
  199. off = 0;
  200. }
  201. string selectedtab;
  202. i = params.find("t");
  203. if (i != params.end()) {
  204. selectedtab = i->second;
  205. }
  206. // print tabs
  207. int count = 0;
  208. if (query.length()) {
  209. count = p->strigi.countHits(query);
  210. }
  211. string activetab;
  212. string activequery = query;
  213. map<string, int> hitcounts;
  214. if (count) {
  215. map<string, string> tabs;
  216. bool doother = true;
  217. tabs = readTabQueries();
  218. if (tabs.size() == 0) {
  219. tabs["Images"] = "mimeType:image*";
  220. tabs["Mail"] = "mimeType:message/*";
  221. tabs["Web"] = "mimeType:text/html";
  222. tabs["Text"] = "mimeType:text/*";
  223. }
  224. map<string, string>::const_iterator j;
  225. string otherq = query;
  226. for (j = tabs.begin(); j != tabs.end(); ++j) {
  227. string q = query;
  228. if (q != j->second) {
  229. q += ' ' + j->second;
  230. }
  231. int c = p->strigi.countHits(q);
  232. if (c > 0) {
  233. hitcounts[j->first] = c;
  234. doother &= c < count;
  235. otherq += " -" + j->second;
  236. if (j->first == selectedtab || activetab.size() == 0) {
  237. activetab = j->first;
  238. activequery = q;
  239. }
  240. }
  241. }
  242. doother &= hitcounts.size() > 0;
  243. int othercount = 0;
  244. if (doother) {
  245. othercount = p->strigi.countHits(otherq);
  246. if (othercount > 0) {
  247. hitcounts["Other"] = othercount;
  248. if (selectedtab == "Other") {
  249. activetab = selectedtab;
  250. activequery = otherq;
  251. }
  252. }
  253. }
  254. }
  255. // print gui
  256. out << "<div class='control' style='text-align:right;' padding='5px'>";
  257. out << "<form method='get'>";
  258. out << "<input type='text' name='q' value='" << query << "'/>";
  259. out << "<input type='hidden' name='m' value='" << max << "'/>";
  260. out << "<input type='hidden' name='t' value='" << activetab << "'/>";
  261. out << "<input type='submit' value='search'/>";
  262. int activecount = count;
  263. if (hitcounts.size() == 0 && count > 0) {
  264. out << " Found " << count << " results.";
  265. } else {
  266. map<string, int>::const_iterator l;
  267. for (l = hitcounts.begin(); l != hitcounts.end(); ++l) {
  268. if (l->first == activetab) {
  269. out << " <a class='activetab' ";
  270. activecount = l->second;
  271. } else {
  272. out << " <a class='tab' ";
  273. }
  274. out << "href='?q=" << query << "&t=" << l->first << "'>"
  275. << l->first << "&nbsp;(" << l->second << ")" << "</a>";
  276. }
  277. }
  278. out << "</form></div>\n";
  279. if (activequery.length()) {
  280. // print results
  281. const ClientInterface::Hits hits = p->strigi.getHits(activequery,
  282. max, off);
  283. if (hits.hits.size()) {
  284. out << "<div class='hits'>";
  285. p->printSearchResults(out, hits, activequery);
  286. out << "</div>";
  287. }
  288. }
  289. if (activecount > max) {
  290. ostringstream oss;
  291. oss << ".?q=" << query << "&m=" << max << "&t=" << activetab << "&o=";
  292. out << "<div class='pager'>";
  293. int o = 0;
  294. int n = 1;
  295. while (o < activecount && n <= 20) {
  296. out << "<a href='" << oss.str() << o << "'>" << n << "</a> ";
  297. o += max;
  298. n++;
  299. }
  300. out << "</div>";
  301. }
  302. }
  303. void
  304. getFields(set<string>& fields, const Query& query) {
  305. copy(query.fields().begin(), query.fields().end(),
  306. inserter(fields, fields.begin()));
  307. for (vector<Query>::const_iterator i = query.subQueries().begin();
  308. i != query.subQueries().end(); ++i) {
  309. getFields(fields, *i);
  310. }
  311. }
  312. void
  313. getTerms(set<string>& terms, const Query& q) {
  314. if (q.term().string().size() && !q.negate()) {
  315. terms.insert(q.term().string());
  316. }
  317. for (vector<Query>::const_iterator i = q.subQueries().begin();
  318. i != q.subQueries().end(); ++i) {
  319. getTerms(terms, *i);
  320. }
  321. }
  322. string
  323. StrigiHtmlGui::Private::highlightTerms(const string& t, const Query& q) const {
  324. vector<string> terms;
  325. set<string> termset;
  326. getTerms(termset, q);
  327. copy(termset.begin(), termset.end(), back_inserter(terms));
  328. string out = h->highlight(t, terms);
  329. return out;
  330. }
  331. void
  332. StrigiHtmlGui::printMenu(ostream& out, const string& path,
  333. const map<string, string> &params) {
  334. out << "<div class='menu'>" << endl;
  335. out << "<a href='/'>search</a> " << endl;
  336. out << "<a href='/status'>status</a> " << endl;
  337. out << "<a href='/config'>preferences</a> " << endl;
  338. out << "<a href='/help'>help</a> " << endl;
  339. out << "<a href='/about'>about</a> " << endl;
  340. out << "</div>" << endl;
  341. }
  342. void
  343. StrigiHtmlGui::printIndexedDirs(ostream& out, const string& path,
  344. const map<string, string> &params) {
  345. set<string> dirs = p->strigi.getIndexedDirectories();
  346. map<string,string>::const_iterator i = params.find("adddir");
  347. if (i != params.end()) {
  348. DIR* dir = opendir(i->second.c_str());
  349. if (dir) {
  350. dirs.insert(i->second);
  351. closedir(dir);
  352. p->strigi.setIndexedDirectories(dirs);
  353. out << "<p>Directory added. Don't forget to start indexing.</p>";
  354. }
  355. }
  356. i = params.find("deldir");
  357. if (i != params.end()) {
  358. size_t oldsize = dirs.size();
  359. dirs.erase(i->second);
  360. if (dirs.size() != oldsize) {
  361. p->strigi.setIndexedDirectories(dirs);
  362. }
  363. }
  364. out << "<table>";
  365. set<string>::const_iterator j;
  366. for (j = dirs.begin(); j != dirs.end(); ++j) {
  367. out << "<tr><td><form method='get'>"
  368. "<input type='hidden' name='deldir' value='" << *j << "'/>"
  369. "<input type='submit' value='delete directory'/></form></td><td>"
  370. << *j << "</td></tr>";
  371. }
  372. out << "<form><tr><td><input type='submit' value='add directory'/></td>"
  373. "<td><input name='adddir' type='file'/></td></tr></form>";
  374. out << "</table>";
  375. }
  376. void
  377. StrigiHtmlGui::printPage(ostream& out, const string& path,
  378. const map<string, string> &params) {
  379. printHeader(out, path, params);
  380. bool running = p->strigi.getStatus().size() > 0;
  381. if (strncmp(path.c_str(), "help", 4) == 0) {
  382. printHelp(out, path, params);
  383. } else if (strncmp(path.c_str(), "about", 5) == 0) {
  384. printAbout(out, path, params);
  385. } else if (running && strncmp(path.c_str(), "config", 6) == 0) {
  386. printConfig(out, path, params);
  387. } else if (strncmp(path.c_str(), "status", 6) == 0) {
  388. printStatus(out, path, params);
  389. } else if (running) {
  390. printSearch(out, path, params);
  391. } else {
  392. printStatus(out, path, params);
  393. }
  394. printFooter(out, path, params);
  395. }
  396. StrigiHtmlGui::Private::Private(HtmlHelper* helper) :h(helper) {
  397. string homedir = getenv("HOME");
  398. strigi.setSocketName(homedir+"/.strigi/socket");
  399. }
  400. string
  401. toSizeString(int s) {
  402. ostringstream o;
  403. if (s > 1024) {
  404. o << (s+512)/1024 << "k";
  405. } else {
  406. o << s << " bytes";
  407. }
  408. return o.str();
  409. }
  410. void
  411. StrigiHtmlGui::Private::printSearchResult(ostream& out,
  412. const Strigi::IndexedDocument& doc, const Query& query) const {
  413. multimap<string, string>::const_iterator t;
  414. string link, icon, name, folder;
  415. int depth = 0;
  416. t = doc.properties.find("depth");
  417. if (t != doc.properties.end()) {
  418. depth = atoi(t->second.c_str());
  419. }
  420. link = h->mapLinkUrl(doc.uri, depth);
  421. icon = h->mapMimetypeIcon(doc.uri, doc.mimetype);
  422. if (icon.length()) {
  423. icon = "<div class='iconbox'><img class='icon' src='"+icon;
  424. icon += "'/></div>\n";
  425. }
  426. t = doc.properties.find("title");
  427. if (t == doc.properties.end()) {
  428. t = doc.properties.find("subject");
  429. }
  430. size_t l = doc.uri.rfind('/');
  431. if (t != doc.properties.end()) {
  432. name = t->second.c_str();
  433. } else if (l != string::npos) {
  434. name = doc.uri.substr(l+1);
  435. } else {
  436. name = doc.uri;
  437. }
  438. name = h->escapeString(name);
  439. if (l != string::npos) {
  440. folder = doc.uri.substr(0, l);
  441. }
  442. out << "<div class='hit'>" << icon << "<h2><a href='" << link << "'>";
  443. out << name << "</a></h2>";
  444. /* out << "<br/>score: ";
  445. out << doc.score << ", mime-type: " << doc.mimetype.c_str() << ", size: ";
  446. out << doc.size << ", last modified: " << h->formatDate(doc.mtime);*/
  447. string fragment = h->escapeString(doc.fragment);
  448. fragment = highlightTerms(fragment, query);
  449. out << "<div class='fragment'>" << fragment << "</div>";
  450. string path = h->escapeString(doc.uri);
  451. out << "<div class='path'>";
  452. string::size_type p = path.find('/');
  453. string::size_type pp = 0;
  454. string subpath;
  455. while (p != string::npos) {
  456. subpath = path.substr(pp, p-pp+1);
  457. link = h->mapLinkUrl(path.substr(0, p));
  458. out << "<a href='" << link << "'>" << subpath << "</a>"
  459. // the images are meant to allow line break between characters
  460. //"<img class='softbreak' src=''/><span style=''>/</span>"
  461. << h->getPathCharacterSeparator();
  462. //"<img class='softbreak' src=''/>";
  463. pp = p+1;
  464. p = path.find('/', pp);
  465. }
  466. subpath = path.substr(pp, path.length()-pp+1);
  467. link = h->mapLinkUrl(doc.uri, depth);
  468. out << "<a href='" << link << "'>" << subpath << "</a>";
  469. out << " - "
  470. << toSizeString((int)doc.size) << " - "
  471. << h->mimetypeDescription(doc.mimetype) << "</div>";
  472. /*out << "<table>";
  473. map<string, string>::const_iterator j;
  474. for (j = doc.properties.begin(); j != doc.properties.end(); ++j) {
  475. out << "<tr><td>" << j->first << ":</td><td>" << j->second.c_str()
  476. << "</td></tr>";
  477. }
  478. out << "</table>";*/
  479. out << "</div>";
  480. }
  481. void
  482. StrigiHtmlGui::Private::printSearchResults(ostream& out,
  483. const ClientInterface::Hits& hits, const string& q) const {
  484. QueryParser parser;
  485. Query query = parser.buildQuery(q);
  486. vector<Strigi::IndexedDocument>::const_iterator i;
  487. for (i = hits.hits.begin(); i != hits.hits.end(); ++i) {
  488. printSearchResult(out, *i, query);
  489. }
  490. }
  491. /**
  492. * Read the tab queries from a configuration file.
  493. * This file is, in the current implementation, located at ~/.strigi/tabqueries.
  494. * The format is a tab separated table with two columns: the name and the query.
  495. **/
  496. map<string, string>
  497. StrigiHtmlGui::readTabQueries() const {
  498. map<string, string> tabs;
  499. string path = getenv("HOME");
  500. path += "/.strigi/tabqueries";
  501. ifstream in;
  502. in.open(path.c_str());
  503. string s;
  504. do {
  505. getline(in, s);
  506. if (s.size()) {
  507. size_t p = s.find('\t');
  508. if (p != string::npos) {
  509. string name = s.substr(0, p);
  510. string value = s.substr(p);
  511. tabs[name] = value;
  512. }
  513. }
  514. } while (!in.eof() && in.good());
  515. in.close();
  516. return tabs;
  517. }
  518. void
  519. StrigiHtmlGui::printSearchResults(std::ostream& out,
  520. const ClientInterface::Hits& hits, const std::string& query) {
  521. printHtmlHeader(out);
  522. p->printSearchResults(out, hits, query);
  523. out << "</body></html>";
  524. }