-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
8 changed files
with
261 additions
and
8 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
// Copyright 2023 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package issues | ||
|
||
import ( | ||
"context" | ||
"strconv" | ||
"sync" | ||
"time" | ||
|
||
"github.com/meilisearch/meilisearch-go" | ||
) | ||
|
||
var _ Indexer = &MeilisearchIndexer{} | ||
|
||
// MeilisearchIndexer implements Indexer interface | ||
type MeilisearchIndexer struct { | ||
client *meilisearch.Client | ||
indexerName string | ||
available bool | ||
availabilityCallback func(bool) | ||
stopTimer chan struct{} | ||
lock sync.RWMutex | ||
} | ||
|
||
// MeilisearchIndexer creates a new meilisearch indexer | ||
func NewMeilisearchIndexer(url, apiKey, indexerName string) (*MeilisearchIndexer, error) { | ||
client := meilisearch.NewClient(meilisearch.ClientConfig{ | ||
Host: url, | ||
APIKey: apiKey, | ||
}) | ||
|
||
indexer := &MeilisearchIndexer{ | ||
client: client, | ||
indexerName: indexerName, | ||
available: true, | ||
stopTimer: make(chan struct{}), | ||
} | ||
|
||
ticker := time.NewTicker(10 * time.Second) | ||
go func() { | ||
for { | ||
select { | ||
case <-ticker.C: | ||
indexer.checkAvailability() | ||
case <-indexer.stopTimer: | ||
ticker.Stop() | ||
return | ||
} | ||
} | ||
}() | ||
|
||
return indexer, nil | ||
} | ||
|
||
// Init will initialize the indexer | ||
func (b *MeilisearchIndexer) Init() (bool, error) { | ||
_, err := b.client.GetIndex(b.indexerName) | ||
if err == nil { | ||
return true, nil | ||
} | ||
_, err = b.client.CreateIndex(&meilisearch.IndexConfig{ | ||
Uid: b.indexerName, | ||
PrimaryKey: "id", | ||
}) | ||
if err != nil { | ||
return false, b.checkError(err) | ||
} | ||
|
||
_, err = b.client.Index(b.indexerName).UpdateFilterableAttributes(&[]string{"repo_id"}) | ||
return false, b.checkError(err) | ||
} | ||
|
||
// SetAvailabilityChangeCallback sets callback that will be triggered when availability changes | ||
func (b *MeilisearchIndexer) SetAvailabilityChangeCallback(callback func(bool)) { | ||
b.lock.Lock() | ||
defer b.lock.Unlock() | ||
b.availabilityCallback = callback | ||
} | ||
|
||
// Ping checks if meilisearch is available | ||
func (b *MeilisearchIndexer) Ping() bool { | ||
b.lock.RLock() | ||
defer b.lock.RUnlock() | ||
return b.available | ||
} | ||
|
||
// Index will save the index data | ||
func (b *MeilisearchIndexer) Index(issues []*IndexerData) error { | ||
if len(issues) == 0 { | ||
return nil | ||
} | ||
for _, issue := range issues { | ||
_, err := b.client.Index(b.indexerName).AddDocuments(issue) | ||
if err != nil { | ||
return b.checkError(err) | ||
} | ||
} | ||
// TODO: bulk send index data | ||
return nil | ||
} | ||
|
||
// Delete deletes indexes by ids | ||
func (b *MeilisearchIndexer) Delete(ids ...int64) error { | ||
if len(ids) == 0 { | ||
return nil | ||
} | ||
|
||
for _, id := range ids { | ||
_, err := b.client.Index(b.indexerName).DeleteDocument(strconv.FormatInt(id, 10)) | ||
if err != nil { | ||
return b.checkError(err) | ||
} | ||
} | ||
// TODO: bulk send deletes | ||
return nil | ||
} | ||
|
||
// Search searches for issues by given conditions. | ||
// Returns the matching issue IDs | ||
func (b *MeilisearchIndexer) Search(ctx context.Context, keyword string, repoIDs []int64, limit, start int) (*SearchResult, error) { | ||
filter := make([][]string, 0, len(repoIDs)) | ||
for _, repoID := range repoIDs { | ||
filter = append(filter, []string{"repo_id = " + strconv.FormatInt(repoID, 10)}) | ||
} | ||
searchRes, err := b.client.Index(b.indexerName).Search(keyword, &meilisearch.SearchRequest{ | ||
Filter: filter, | ||
Limit: int64(limit), | ||
Offset: int64(start), | ||
}) | ||
if err != nil { | ||
return nil, b.checkError(err) | ||
} | ||
|
||
hits := make([]Match, 0, len(searchRes.Hits)) | ||
for _, hit := range searchRes.Hits { | ||
hits = append(hits, Match{ | ||
ID: int64(hit.(map[string]interface{})["id"].(float64)), | ||
}) | ||
} | ||
return &SearchResult{ | ||
Total: searchRes.TotalHits, | ||
Hits: hits, | ||
}, nil | ||
} | ||
|
||
// Close implements indexer | ||
func (b *MeilisearchIndexer) Close() { | ||
select { | ||
case <-b.stopTimer: | ||
default: | ||
close(b.stopTimer) | ||
} | ||
} | ||
|
||
func (b *MeilisearchIndexer) checkError(err error) error { | ||
return err | ||
} | ||
|
||
func (b *MeilisearchIndexer) checkAvailability() { | ||
_, err := b.client.Health() | ||
if err != nil { | ||
b.setAvailability(false) | ||
return | ||
} | ||
b.setAvailability(true) | ||
} | ||
|
||
func (b *MeilisearchIndexer) setAvailability(available bool) { | ||
b.lock.Lock() | ||
defer b.lock.Unlock() | ||
|
||
if b.available == available { | ||
return | ||
} | ||
|
||
b.available = available | ||
if b.availabilityCallback != nil { | ||
// Call the callback from within the lock to ensure that the ordering remains correct | ||
b.availabilityCallback(b.available) | ||
} | ||
} |
Oops, something went wrong.