~stolowski/unity-lens-applications/sc-startup

« back to all changes in this revision

Viewing changes to src/unity-package-search.cc

Add libcolumbus fuzzy matching. Fixes: https://bugs.launchpad.net/bugs/1135698.

Approved by Pawel Stolowski.

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
 * Xapian index of the menu contents.
26
26
 */
27
27
 
28
 
using namespace std;
29
 
 
30
28
#include <xapian.h>
31
29
#include <iostream>
32
30
#include <gmenu-tree.h>
33
31
#include <glib.h>
34
32
#include <unity.h>
35
33
#include <string.h>
 
34
#include <vector>
 
35
 
 
36
using namespace std;
36
37
 
37
38
#define SOFTWARE_CENTER_INDEX "/var/cache/software-center/xapian"
38
39
#define QUERY_PARSER_EXACTSEARCH_FLAGS Xapian::QueryParser::FLAG_BOOLEAN|Xapian::QueryParser::FLAG_PHRASE|Xapian::QueryParser::FLAG_LOVEHATE
55
56
#define XAPIAN_VALUE_SCREENSHOT_URLS 185
56
57
#define XAPIAN_VALUE_DESCRIPTION 188
57
58
#define XAPIAN_VALUE_VERSION_INFO 198
 
59
#define XAPIAN_VALUE_EXENAME 294
58
60
#define XAPIAN_VALUE_CURRENCY 201
59
61
 
60
62
#include "unity-package-search.h"
61
 
 
62
 
extern "C"
63
 
{
64
 
  extern gchar* unity_applications_lens_utils_preprocess_string (const gchar* input);
65
 
}
 
63
#include "columbus.hh"
66
64
 
67
65
struct _UnityPackageSearcher
68
66
{
71
69
  Xapian::Enquire     *enquire;
72
70
  Xapian::QueryParser *query_parser;
73
71
  GRand               *random;
 
72
  Columbus::Matcher   *matcher;
 
73
  vector<string>      col_mapping;
 
74
  bool                db_merged;
74
75
};
75
76
 
 
77
static void buildMatcher(UnityPackageSearcher *searcher) {
 
78
    Columbus::Matcher *m  = searcher->matcher;
 
79
    Xapian::Database *db = searcher->db;
 
80
    Columbus::Corpus c;
 
81
    Columbus::Word appnameField("appname");
 
82
    Columbus::Word summaryField("summary");
 
83
    Columbus::Word pkgnameField("pkgname");
 
84
    Columbus::Word exenameField("exename");
 
85
 
 
86
    for(Xapian::PostingIterator post = db->postlist_begin(""); post != db->postlist_end(""); post++) {
 
87
        Xapian::Document xdoc = db->get_document(*post);
 
88
        DocumentID id;
 
89
        if(searcher->db_merged) {
 
90
            searcher->col_mapping.push_back(xdoc.get_value(XAPIAN_VALUE_APPNAME));
 
91
            id = searcher->col_mapping.size()-1;
 
92
        } else {
 
93
            id = xdoc.get_docid();
 
94
        }
 
95
        Columbus::Document cdoc(id);
 
96
        std::string val;
 
97
 
 
98
        val = xdoc.get_value(XAPIAN_VALUE_APPNAME);
 
99
        if(!val.empty())
 
100
            cdoc.addText(appnameField, val.c_str());
 
101
        val = xdoc.get_value(XAPIAN_VALUE_SUMMARY);
 
102
        if(!val.empty())
 
103
            cdoc.addText(summaryField, val.c_str());
 
104
        val = xdoc.get_value(XAPIAN_VALUE_PKGNAME);
 
105
        if(!val.empty())
 
106
            cdoc.addText(pkgnameField, val.c_str());
 
107
        val = xdoc.get_value(XAPIAN_VALUE_EXENAME);
 
108
        if(!val.empty())
 
109
            cdoc.addText(exenameField, val.c_str());
 
110
        c.addDocument(cdoc);
 
111
    }
 
112
    m->index(c);
 
113
    m->getErrorValues().addStandardErrors();
 
114
    m->getErrorValues().setSubstringMode();
 
115
    m->getIndexWeights().setWeight(summaryField, 0.5);
 
116
}
 
117
 
 
118
 
 
119
extern "C"
 
120
{
 
121
  extern gchar* unity_applications_lens_utils_preprocess_string (const gchar* input);
 
122
}
 
123
 
76
124
/* A Xapian::Sorter that respects the collation rules for the current locale */
77
125
class LocaleKeyMaker : public Xapian::KeyMaker
78
126
{
264
312
        dum2 = strstr (dum1, " "); // const
265
313
        dum2 == NULL ? : *dum2 = '\0'; // const
266
314
        dum2 = g_path_get_basename (dum1); // alloc
 
315
        doc.add_value(XAPIAN_VALUE_EXENAME, dum2);
267
316
        indexer->index_text (dum2, 2);
268
317
        g_free (dum1);
269
318
        dum1 = g_strdelimit (dum2, "-", '_'); // in place switch, dum1 own mem
299
348
  UnityPackageSearcher *searcher;
300
349
  Xapian::WritableDatabase *db;
301
350
 
302
 
  searcher = g_new0 (UnityPackageSearcher, 1);
 
351
  searcher = new UnityPackageSearcher;
303
352
  db = new Xapian::WritableDatabase ();
304
353
  searcher->db = db;
305
354
  searcher->db->add_database (Xapian::InMemory::open ());
307
356
  init_searcher (searcher);
308
357
 
309
358
  /* Index the menu recursively */
 
359
  searcher->db_merged = false;
310
360
  Xapian::TermGenerator *indexer = new Xapian::TermGenerator ();
311
361
  index_menu_item (db, indexer, gmenu_tree_get_root_directory (menu));
312
362
  delete indexer;
313
363
  db->flush ();
314
364
 
 
365
  searcher->matcher = new Columbus::Matcher();
 
366
  buildMatcher(searcher);
 
367
 
315
368
  return searcher;
316
369
}
317
370
 
323
376
  UnityPackageSearcher *searcher;
324
377
  gchar *agent = NULL;
325
378
 
326
 
  searcher = g_new0 (UnityPackageSearcher, 1);
 
379
  searcher = new UnityPackageSearcher;
327
380
 
328
381
  // Xapian initialization
329
382
  try
351
404
  }
352
405
 
353
406
  init_searcher (searcher);
 
407
  searcher->db_merged = true;
 
408
  searcher->matcher = new Columbus::Matcher();
 
409
  buildMatcher(searcher);
354
410
 
355
411
  return searcher;
356
412
}
364
420
  delete searcher->sorter;
365
421
  delete searcher->enquire;
366
422
  delete searcher->query_parser;
 
423
  delete searcher->matcher;
367
424
  g_rand_free (searcher->random);
368
 
  g_free (searcher);
 
425
  delete searcher;
369
426
}
370
427
 
371
428
static UnityPackageInfo*
425
482
  g_slice_free (UnityPackageInfo, pkginfo);
426
483
}
427
484
 
428
 
UnityPackageSearchResult*
429
 
unity_package_searcher_search (UnityPackageSearcher  *searcher,
430
 
                               const gchar           *search_string,
431
 
                               guint                  max_hits,
432
 
                               UnityPackageSearchType search_type,
433
 
                               UnityPackageSort       sort)
 
485
Xapian::Document get_doc_from_col_match(UnityPackageSearcher *searcher, DocumentID id) {
 
486
    if(searcher->db_merged) {
 
487
        string name = searcher->col_mapping[id];
 
488
        string query = "AA\"";
 
489
        query += name;
 
490
        query += "\"";
 
491
        Xapian::QueryParser p;
 
492
        Xapian::Query q;
 
493
        Xapian::Enquire e(*searcher->db);
 
494
        Xapian::MSet matches;
 
495
        p.set_database(*searcher->db);
 
496
        q = p.parse_query(query);
 
497
        e.set_query(q);
 
498
        matches = e.get_mset(0, 1);
 
499
        return matches.begin().get_document();
 
500
    } else {
 
501
        return searcher->db->get_document(id);
 
502
    }
 
503
}
 
504
 
 
505
static UnityPackageSearchResult*
 
506
xapian_search (UnityPackageSearcher  *searcher,
 
507
               const gchar           *search_string,
 
508
               guint                  max_hits,
 
509
               UnityPackageSearchType search_type,
 
510
               UnityPackageSort       sort)
434
511
{
435
512
  UnityPackageSearchResult* result;
436
 
 
437
 
  g_return_val_if_fail (searcher != NULL, NULL);
438
 
  g_return_val_if_fail (search_string != NULL, NULL);
439
 
 
440
513
  string _search_string (search_string);
441
514
  Xapian::Query query;
 
515
 
442
516
  try
443
517
  {
444
518
    switch (search_type)
455
529
        break;
456
530
    }
457
531
  }
458
 
  catch (Xapian::Error e)
 
532
  catch (Xapian::Error &e)
459
533
  {
460
534
    g_warning ("Error parsing query '%s': %s", search_string, e.get_msg().c_str());
461
535
    return g_slice_new0 (UnityPackageSearchResult);
462
536
  }
463
 
  //cout << "Performing query `" << query.get_description() << "'" << endl;
464
537
 
465
538
  switch (sort)
466
539
  {
488
561
    // Retrieve the results, note that we build the result->results
489
562
    // list in reverse order and then reverse it before we return it
490
563
    result->num_hits = matches.get_matches_estimated ();
 
564
 
491
565
    for (Xapian::MSetIterator i = matches.begin(); i != matches.end(); ++i)
492
 
    {
493
 
      try
494
 
      {
495
 
        Xapian::Document doc = i.get_document();
496
 
        UnityPackageInfo *pkginfo = _pkginfo_from_document (doc);
497
 
        pkginfo->relevancy = i.get_percent ();
498
 
        result->results = g_slist_prepend (result->results, pkginfo);
499
 
      }
500
 
      catch (Xapian::Error e)
501
 
      {
502
 
        g_warning ("Unable to read document from result set: %s", e.get_msg().c_str());
503
 
      }
504
 
    }
 
566
      {
 
567
        try
 
568
          {
 
569
            Xapian::Document doc = i.get_document();
 
570
            UnityPackageInfo *pkginfo = _pkginfo_from_document (doc);
 
571
            pkginfo->relevancy = i.get_percent();
 
572
            result->results = g_slist_prepend (result->results, pkginfo);
 
573
          }
 
574
        catch (Xapian::Error &e)
 
575
          {
 
576
            g_warning ("Unable to read document from result set: %s",
 
577
                       e.get_msg().c_str());
 
578
          }
 
579
      }
505
580
 
506
581
    result->results = g_slist_reverse (result->results);
507
582
  }
508
 
  catch(const Xapian::Error e)
 
583
  catch(const Xapian::Error &e)
509
584
  {
510
585
    g_warning ("Error running query '%s': %s", search_string, e.get_msg().c_str());
511
586
  }
513
588
  return result;
514
589
}
515
590
 
 
591
static UnityPackageSearchResult*
 
592
libcolumbus_search(UnityPackageSearcher *searcher, const char *str) {
 
593
    Columbus::MatchResults results;
 
594
    UnityPackageSearchResult* result;
 
595
 
 
596
    result = g_slice_new0 (UnityPackageSearchResult);
 
597
    searcher->matcher->match(str, results);
 
598
    for (size_t i = 0; i < results.size(); ++i)
 
599
      {
 
600
        try
 
601
          {
 
602
            Xapian::Document doc = get_doc_from_col_match(searcher, results.getDocumentID(i));
 
603
            UnityPackageInfo *pkginfo = _pkginfo_from_document (doc);
 
604
            pkginfo->relevancy = results.getRelevancy(i);
 
605
            result->results = g_slist_prepend (result->results, pkginfo);
 
606
          }
 
607
        catch (Xapian::Error &e)
 
608
          {
 
609
            g_warning ("Unable to read document from result set: %s",
 
610
                       e.get_msg().c_str());
 
611
          }
 
612
      }
 
613
 
 
614
    result->results = g_slist_reverse (result->results);
 
615
    return result;
 
616
}
 
617
 
 
618
UnityPackageSearchResult*
 
619
unity_package_searcher_search (UnityPackageSearcher  *searcher,
 
620
                               const gchar           *search_string,
 
621
                               guint                  max_hits,
 
622
                               UnityPackageSearchType search_type,
 
623
                               UnityPackageSort       sort)
 
624
{
 
625
 
 
626
  g_return_val_if_fail (searcher != NULL, NULL);
 
627
  g_return_val_if_fail (search_string != NULL, NULL);
 
628
 
 
629
  bool has_category = strstr(search_string, "category:") != NULL;
 
630
  const char *col_query_str = strstr(search_string, "AND");
 
631
  if(has_category || !col_query_str) {
 
632
      return xapian_search(searcher, search_string, max_hits, search_type, sort);
 
633
  } else {
 
634
      col_query_str += 3;
 
635
      return libcolumbus_search(searcher, col_query_str);
 
636
  }
 
637
}
 
638
 
 
639
 
516
640
/**
517
641
 * Get applications matching given xapian filter query and additionally filter results out
518
642
 * using AppFilterCallback, until n_apps matching apps are found.
618
742
          n_unique++;
619
743
        }
620
744
      }
621
 
      catch (Xapian::Error e)
 
745
      catch (Xapian::Error &e)
622
746
      {
623
747
        g_warning ("Error getting random apps: %s", e.get_msg().c_str());
624
748
        continue;
653
777
        }
654
778
      }
655
779
    }
656
 
    catch (Xapian::Error e)
 
780
    catch (Xapian::Error &e)
657
781
    {
658
782
      g_debug ("Error getting random apps for query '%s': %s", filter_query, e.get_msg().c_str());
659
783
      return g_slice_new0 (UnityPackageSearchResult);