diff --git a/database.go b/database.go index a6c4a77a90043771fe74ef3f053f9c32cd0b4ef4..d488ccb78b358e3c9bac00d2e38c46d9125b9a18 100644 --- a/database.go +++ b/database.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "encoding/gob" "errors" + "log" "math/rand" "os" "path/filepath" @@ -78,6 +79,7 @@ type flatBook struct { Description string `json:"description"` ISBN []string `json:"isbn"` Unique []string `json:"_unique"` + Suggest []string `json:"_suggest"` } func (f *flatBook) Type() string { @@ -85,12 +87,17 @@ func (f *flatBook) Type() string { } func flatten(book *Book) *flatBook { + suggest := []string{book.Metadata.Title} + if len(book.Metadata.Creator) > 0 { + suggest = append(suggest, book.Metadata.Creator...) + } return &flatBook{ Title: book.Metadata.Title, Author: book.Metadata.Creator, Description: book.Metadata.Description, ISBN: book.Metadata.ISBN, Unique: book.Metadata.Uniques(), + Suggest: suggest, } } @@ -112,18 +119,51 @@ func metadataDocumentMapping() *bleve.DocumentMapping { keywordFieldMapping.Analyzer = "keyword" keywordFieldMapping.IncludeInAll = false + suggestFieldMapping := bleve.NewTextFieldMapping() + suggestFieldMapping.Store = false + suggestFieldMapping.Analyzer = "edgeNgram" + suggestFieldMapping.IncludeTermVectors = false + suggestFieldMapping.IncludeInAll = false + md.AddFieldMappingsAt("title", textFieldMapping) md.AddFieldMappingsAt("author", authorFieldMapping) md.AddFieldMappingsAt("description", textFieldMapping) md.AddFieldMappingsAt("isbn", keywordFieldMapping) md.AddFieldMappingsAt("_unique", keywordFieldMapping) + md.AddFieldMappingsAt("_suggest", suggestFieldMapping) return md } func defaultIndexMapping() *bleve.IndexMapping { i := bleve.NewIndexMapping() + + err := i.AddCustomTokenFilter("edgeNgram325", + map[string]interface{}{ + "type": "edge_ngram", + "min": 3.0, + "max": 25.0, + }) + if err != nil { + log.Fatal(err) + } + + err = i.AddCustomAnalyzer("edgeNgram", + map[string]interface{}{ + "type": "custom", + "tokenizer": "unicode", + "token_filters": []string{ + "to_lower", + "stop_en", + "edgeNgram325", + }, + }) + if err != nil { + log.Fatal(err) + } + i.AddDocumentMapping("ebook", metadataDocumentMapping()) + i.DefaultAnalyzer = defaultTextAnalyzer i.DefaultType = "ebook" return i @@ -372,8 +412,9 @@ func (db *Database) Search(queryStr string, offset, limit int) (*SearchResult, e } // Autocomplete runs a fuzzy search for a term. -func (db *Database) Autocomplete(term string) (*SearchResult, error) { - return db.doSearch(bleve.NewFuzzyQuery(term), 0, 20) +func (db *Database) Suggest(term string) (*SearchResult, error) { + query := bleve.NewTermQuery(term).SetField("_suggest") + return db.doSearch(query, 0, 20) } // Find a book matching the given metadata, if possible. diff --git a/database_test.go b/database_test.go index 4d207364c0b480814ea0da6bf5fbf5966a6b5830..72665f813b74bb2029454f9578db07c9bbb86538 100644 --- a/database_test.go +++ b/database_test.go @@ -229,19 +229,21 @@ func TestDatabase_Find(t *testing.T) { } } -func TestDatabase_Autocomplete(t *testing.T) { +func TestDatabase_Suggest(t *testing.T) { td, db := newTestDatabase(t) defer td.Close() - r, err := db.Autocomplete("jules") - if err != nil { - t.Fatal(err) - } - if r.NumResults == 0 { - t.Error("No results") + for _, s := range []string{"jul", "jule", "jules", "ver", "vern", "verne", "twent", "thous"} { + r, err := db.Suggest(s) + if err != nil { + t.Fatal(err) + } + if r.NumResults == 0 { + t.Errorf("No results for '%s'", s) + } } - r, err = db.Autocomplete("foo") + r, err := db.Suggest("foo") if err != nil { t.Fatal(err) } diff --git a/web.go b/web.go index 2335689cae4aebc95823d84fe1a27df050e37de0..4b00b064d3491ec462e34ee5c7ecc97afdb76b4d 100644 --- a/web.go +++ b/web.go @@ -100,14 +100,14 @@ type autocompleteResult struct { Label string `json:"label"` } -func (s *uiServer) handleAutocomplete(w http.ResponseWriter, req *http.Request) { +func (s *uiServer) handleSuggest(w http.ResponseWriter, req *http.Request) { term := req.FormValue("term") if term == "" { http.Error(w, "No Query", http.StatusBadRequest) return } - result, err := s.db.Autocomplete(term) + result, err := s.db.Suggest(term) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -359,7 +359,7 @@ func NewHttpServer(db *Database, storage, cache *FileStorage, addr string) *http r.Handle("/read/{id:[0-9]+}/{fid:[0-9]+}", uisrv.withFile(uisrv.handleReadBook)) r.Handle("/dl/{id:[0-9]+}/{fid:[0-9]+}", uisrv.withFile(uisrv.handleDownloadBook)) r.HandleFunc("/opensearch.xml", handleOpenSearchXml) - r.HandleFunc("/suggest", uisrv.handleAutocomplete) + r.HandleFunc("/suggest", uisrv.handleSuggest) r.HandleFunc("/search", uisrv.handleSearch) r.HandleFunc("/", uisrv.handleHome)