mirror of
https://github.com/PeernetOfficial/core.git
synced 2026-07-16 18:37:51 +01:00
changes to explore to take parameter id in /explore
This commit is contained in:
13
webapi/Download_test.go
Normal file
13
webapi/Download_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package webapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test function
|
||||
func TestDecodeBlake3Hash(t *testing.T) {
|
||||
hash, bool := DecodeBlake3Hash("")
|
||||
fmt.Println(hash)
|
||||
fmt.Println(bool)
|
||||
}
|
||||
505
webapi/Search.go
505
webapi/Search.go
@@ -16,61 +16,61 @@ Author: Peter Kleissner
|
||||
package webapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// SearchRequest is the information from the end-user for the search. Filters and sort order may be applied when starting the search, or at runtime when getting the results.
|
||||
type SearchRequest struct {
|
||||
Term string `json:"term"` // Search term.
|
||||
Timeout int `json:"timeout"` // Timeout in seconds. 0 means default. This is the entire time the search may take. Found results are still available after this timeout.
|
||||
MaxResults int `json:"maxresults"` // Total number of max results. 0 means default.
|
||||
DateFrom string `json:"datefrom"` // Date from, both from/to are required if set. Format "2006-01-02 15:04:05".
|
||||
DateTo string `json:"dateto"` // Date to, both from/to are required if set. Format "2006-01-02 15:04:05".
|
||||
Sort int `json:"sort"` // See SortX.
|
||||
TerminateID []uuid.UUID `json:"terminate"` // Optional: Previous search IDs to terminate. This is if the user makes a new search from the same tab. Same as first calling /search/terminate.
|
||||
FileType int `json:"filetype"` // File type such as binary, text document etc. See core.TypeX. -1 = not used.
|
||||
FileFormat int `json:"fileformat"` // File format such as PDF, Word, Ebook, etc. See core.FormatX. -1 = not used.
|
||||
SizeMin int `json:"sizemin"` // Min file size in bytes. -1 = not used.
|
||||
SizeMax int `json:"sizemax"` // Max file size in bytes. -1 = not used.
|
||||
Term string `json:"term"` // Search term.
|
||||
Timeout int `json:"timeout"` // Timeout in seconds. 0 means default. This is the entire time the search may take. Found results are still available after this timeout.
|
||||
MaxResults int `json:"maxresults"` // Total number of max results. 0 means default.
|
||||
DateFrom string `json:"datefrom"` // Date from, both from/to are required if set. Format "2006-01-02 15:04:05".
|
||||
DateTo string `json:"dateto"` // Date to, both from/to are required if set. Format "2006-01-02 15:04:05".
|
||||
Sort int `json:"sort"` // See SortX.
|
||||
TerminateID []uuid.UUID `json:"terminate"` // Optional: Previous search IDs to terminate. This is if the user makes a new search from the same tab. Same as first calling /search/terminate.
|
||||
FileType int `json:"filetype"` // File type such as binary, text document etc. See core.TypeX. -1 = not used.
|
||||
FileFormat int `json:"fileformat"` // File format such as PDF, Word, Ebook, etc. See core.FormatX. -1 = not used.
|
||||
SizeMin int `json:"sizemin"` // Min file size in bytes. -1 = not used.
|
||||
SizeMax int `json:"sizemax"` // Max file size in bytes. -1 = not used.
|
||||
}
|
||||
|
||||
// Sort orders
|
||||
const (
|
||||
SortNone = 0 // No sorting. Results are returned as they come in.
|
||||
SortRelevanceAsc = 1 // Least relevant results first.
|
||||
SortRelevanceDec = 2 // Most relevant results first.
|
||||
SortDateAsc = 3 // Oldest first.
|
||||
SortDateDesc = 4 // Newest first.
|
||||
SortNameAsc = 5 // File name ascending. The folder name is not used for sorting.
|
||||
SortNameDesc = 6 // File name descending. The folder name is not used for sorting.
|
||||
SortSizeAsc = 7 // File size ascending. Smallest files first.
|
||||
SortSizeDesc = 8 // File size descending. Largest files first.
|
||||
SortSharedByCountAsc = 9 // Shared by count ascending. Files that are shared by the least count of peers first.
|
||||
SortSharedByCountDesc = 10 // Shared by count descending. Files that are shared by the most count of peers first.
|
||||
SortNone = 0 // No sorting. Results are returned as they come in.
|
||||
SortRelevanceAsc = 1 // Least relevant results first.
|
||||
SortRelevanceDec = 2 // Most relevant results first.
|
||||
SortDateAsc = 3 // Oldest first.
|
||||
SortDateDesc = 4 // Newest first.
|
||||
SortNameAsc = 5 // File name ascending. The folder name is not used for sorting.
|
||||
SortNameDesc = 6 // File name descending. The folder name is not used for sorting.
|
||||
SortSizeAsc = 7 // File size ascending. Smallest files first.
|
||||
SortSizeDesc = 8 // File size descending. Largest files first.
|
||||
SortSharedByCountAsc = 9 // Shared by count ascending. Files that are shared by the least count of peers first.
|
||||
SortSharedByCountDesc = 10 // Shared by count descending. Files that are shared by the most count of peers first.
|
||||
)
|
||||
|
||||
// SearchRequestResponse is the result to the initial search request
|
||||
type SearchRequestResponse struct {
|
||||
ID uuid.UUID `json:"id"` // ID of the search job. This is used to get the results.
|
||||
Status int `json:"status"` // Status of the search: 0 = Success (ID valid), 1 = Invalid Term, 2 = Error Max Concurrent Searches
|
||||
ID uuid.UUID `json:"id"` // ID of the search job. This is used to get the results.
|
||||
Status int `json:"status"` // Status of the search: 0 = Success (ID valid), 1 = Invalid Term, 2 = Error Max Concurrent Searches
|
||||
}
|
||||
|
||||
// SearchResult contains the search results.
|
||||
type SearchResult struct {
|
||||
Status int `json:"status"` // Status: 0 = Success with results, 1 = No more results available, 2 = Search ID not found, 3 = No results yet available keep trying
|
||||
Files []apiFile `json:"files"` // List of files found
|
||||
Statistic interface{} `json:"statistic"` // Statistics of all results (independent from applied filters), if requested. Only set if files are returned (= if statistics changed). See SearchStatisticData.
|
||||
Status int `json:"status"` // Status: 0 = Success with results, 1 = No more results available, 2 = Search ID not found, 3 = No results yet available keep trying
|
||||
Files []apiFile `json:"files"` // List of files found
|
||||
Statistic interface{} `json:"statistic"` // Statistics of all results (independent from applied filters), if requested. Only set if files are returned (= if statistics changed). See SearchStatisticData.
|
||||
}
|
||||
|
||||
// SearchStatistic contains statistics on search results. Statistics are always calculated over all results, regardless of any applied runtime filters.
|
||||
type SearchStatistic struct {
|
||||
SearchStatisticData
|
||||
Status int `json:"status"` // Status: 0 = Success
|
||||
IsTerminated bool `json:"terminated"` // Whether the search is terminated, meaning that statistics won't change
|
||||
SearchStatisticData
|
||||
Status int `json:"status"` // Status: 0 = Success
|
||||
IsTerminated bool `json:"terminated"` // Whether the search is terminated, meaning that statistics won't change
|
||||
}
|
||||
|
||||
const apiDateFormat = "2006-01-02 15:04:05"
|
||||
@@ -84,29 +84,29 @@ Result: 200 on success with JSON SearchRequestResponse
|
||||
*/
|
||||
func (api *WebapiInstance) apiSearch(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var input SearchRequest
|
||||
if err := DecodeJSON(w, r, &input); err != nil {
|
||||
return
|
||||
}
|
||||
var input SearchRequest
|
||||
if err := DecodeJSON(w, r, &input); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if input.Timeout <= 0 {
|
||||
input.Timeout = 20
|
||||
}
|
||||
if input.MaxResults <= 0 {
|
||||
input.MaxResults = 200
|
||||
}
|
||||
if input.Timeout <= 0 {
|
||||
input.Timeout = 20
|
||||
}
|
||||
if input.MaxResults <= 0 {
|
||||
input.MaxResults = 200
|
||||
}
|
||||
|
||||
// Terminate previous searches, if their IDs were supplied. This allows terminating the old search immediately without making a separate /search/terminate request.
|
||||
for _, terminate := range input.TerminateID {
|
||||
if job := api.JobLookup(terminate); job != nil {
|
||||
job.Terminate()
|
||||
api.RemoveJob(job)
|
||||
}
|
||||
}
|
||||
// Terminate previous searches, if their IDs were supplied. This allows terminating the old search immediately without making a separate /search/terminate request.
|
||||
for _, terminate := range input.TerminateID {
|
||||
if job := api.JobLookup(terminate); job != nil {
|
||||
job.Terminate()
|
||||
api.RemoveJob(job)
|
||||
}
|
||||
}
|
||||
|
||||
job := api.dispatchSearch(input)
|
||||
job := api.dispatchSearch(input)
|
||||
|
||||
EncodeJSON(api.backend, w, r, SearchRequestResponse{Status: 0, ID: job.id})
|
||||
EncodeJSON(api.backend, w, r, SearchRequestResponse{Status: 0, ID: job.id})
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -126,82 +126,82 @@ Optional parameters:
|
||||
Result: 200 with JSON structure SearchResult. Check the field status.
|
||||
*/
|
||||
func (api *WebapiInstance) apiSearchResult(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
jobID, err := uuid.Parse(r.Form.Get("id"))
|
||||
if err != nil {
|
||||
http.Error(w, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
limit, err := strconv.Atoi(r.Form.Get("limit"))
|
||||
if err != nil {
|
||||
limit = 100
|
||||
}
|
||||
offset, errOffset := strconv.Atoi(r.Form.Get("offset"))
|
||||
r.ParseForm()
|
||||
jobID, err := uuid.Parse(r.Form.Get("id"))
|
||||
if err != nil {
|
||||
http.Error(w, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
limit, err := strconv.Atoi(r.Form.Get("limit"))
|
||||
if err != nil {
|
||||
limit = 100
|
||||
}
|
||||
offset, errOffset := strconv.Atoi(r.Form.Get("offset"))
|
||||
|
||||
// find the job ID
|
||||
job := api.JobLookup(jobID)
|
||||
if job == nil {
|
||||
EncodeJSON(api.backend, w, r, SearchResult{Status: 2})
|
||||
return
|
||||
}
|
||||
// find the job ID
|
||||
job := api.JobLookup(jobID)
|
||||
if job == nil {
|
||||
EncodeJSON(api.backend, w, r, SearchResult{Status: 2})
|
||||
return
|
||||
}
|
||||
|
||||
// filters and sort parameter
|
||||
if filterReset, _ := strconv.ParseBool(r.Form.Get("reset")); filterReset {
|
||||
fileType, _ := strconv.Atoi(r.Form.Get("filetype"))
|
||||
fileFormat, _ := strconv.Atoi(r.Form.Get("fileformat"))
|
||||
dateFrom := r.Form.Get("from")
|
||||
dateTo := r.Form.Get("to")
|
||||
sort, _ := strconv.Atoi(r.Form.Get("sort"))
|
||||
sizeMin, _ := strconv.Atoi(r.Form.Get("sizemin"))
|
||||
sizeMax, _ := strconv.Atoi(r.Form.Get("sizemax"))
|
||||
// filters and sort parameter
|
||||
if filterReset, _ := strconv.ParseBool(r.Form.Get("reset")); filterReset {
|
||||
fileType, _ := strconv.Atoi(r.Form.Get("filetype"))
|
||||
fileFormat, _ := strconv.Atoi(r.Form.Get("fileformat"))
|
||||
dateFrom := r.Form.Get("from")
|
||||
dateTo := r.Form.Get("to")
|
||||
sort, _ := strconv.Atoi(r.Form.Get("sort"))
|
||||
sizeMin, _ := strconv.Atoi(r.Form.Get("sizemin"))
|
||||
sizeMax, _ := strconv.Atoi(r.Form.Get("sizemax"))
|
||||
|
||||
filter := inputToSearchFilter(sort, fileType, fileFormat, dateFrom, dateTo, sizeMin, sizeMax)
|
||||
filter := inputToSearchFilter(sort, fileType, fileFormat, dateFrom, dateTo, sizeMin, sizeMax)
|
||||
|
||||
job.RuntimeFilter(filter)
|
||||
}
|
||||
job.RuntimeFilter(filter)
|
||||
}
|
||||
|
||||
// query all results
|
||||
var resultFiles []*apiFile
|
||||
if errOffset == nil {
|
||||
resultFiles = job.ReturnResult(offset, limit)
|
||||
} else {
|
||||
resultFiles = job.ReturnNext(limit)
|
||||
}
|
||||
// query all results
|
||||
var resultFiles []*apiFile
|
||||
if errOffset == nil {
|
||||
resultFiles = job.ReturnResult(offset, limit)
|
||||
} else {
|
||||
resultFiles = job.ReturnNext(limit)
|
||||
}
|
||||
|
||||
var result SearchResult
|
||||
result.Files = []apiFile{}
|
||||
var result SearchResult
|
||||
result.Files = []apiFile{}
|
||||
|
||||
// loop over results
|
||||
for n := range resultFiles {
|
||||
result.Files = append(result.Files, *resultFiles[n])
|
||||
}
|
||||
// loop over results
|
||||
for n := range resultFiles {
|
||||
result.Files = append(result.Files, *resultFiles[n])
|
||||
}
|
||||
|
||||
// set the status
|
||||
if len(result.Files) > 0 {
|
||||
if job.IsSearchResults() {
|
||||
result.Status = 0 // 0 = Success with results
|
||||
} else {
|
||||
result.Status = 1 // No more results to expect
|
||||
}
|
||||
} else {
|
||||
switch job.Status {
|
||||
case SearchStatusLive:
|
||||
result.Status = 3 // No results yet available keep trying
|
||||
// set the status
|
||||
if len(result.Files) > 0 {
|
||||
if job.IsSearchResults() {
|
||||
result.Status = 0 // 0 = Success with results
|
||||
} else {
|
||||
result.Status = 1 // No more results to expect
|
||||
}
|
||||
} else {
|
||||
switch job.Status {
|
||||
case SearchStatusLive:
|
||||
result.Status = 3 // No results yet available keep trying
|
||||
|
||||
case SearchStatusTerminated:
|
||||
result.Status = 1 // No more results to expect
|
||||
case SearchStatusTerminated:
|
||||
result.Status = 1 // No more results to expect
|
||||
|
||||
default: // SearchStatusNoIndex, SearchStatusNotStarted
|
||||
result.Status = 1 // No more results to expect
|
||||
}
|
||||
}
|
||||
default: // SearchStatusNoIndex, SearchStatusNotStarted
|
||||
result.Status = 1 // No more results to expect
|
||||
}
|
||||
}
|
||||
|
||||
// embedded statistics?
|
||||
if returnStats, _ := strconv.ParseBool(r.Form.Get("stats")); returnStats {
|
||||
result.Statistic = job.Statistics()
|
||||
}
|
||||
// embedded statistics?
|
||||
if returnStats, _ := strconv.ParseBool(r.Form.Get("stats")); returnStats {
|
||||
result.Statistic = job.Statistics()
|
||||
}
|
||||
|
||||
EncodeJSON(api.backend, w, r, result)
|
||||
EncodeJSON(api.backend, w, r, result)
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -212,80 +212,80 @@ Result: If successful, upgrades to a websocket and sends JSON structure Sear
|
||||
Limit is optional. Not used if ommitted or 0.
|
||||
*/
|
||||
func (api *WebapiInstance) apiSearchResultStream(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
jobID, err := uuid.Parse(r.Form.Get("id"))
|
||||
if err != nil {
|
||||
http.Error(w, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
limit, err := strconv.Atoi(r.Form.Get("limit"))
|
||||
useLimit := err == nil
|
||||
r.ParseForm()
|
||||
jobID, err := uuid.Parse(r.Form.Get("id"))
|
||||
if err != nil {
|
||||
http.Error(w, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
limit, err := strconv.Atoi(r.Form.Get("limit"))
|
||||
useLimit := err == nil
|
||||
|
||||
// look up the job
|
||||
job := api.JobLookup(jobID)
|
||||
if job == nil {
|
||||
EncodeJSON(api.backend, w, r, SearchResult{Status: 2})
|
||||
return
|
||||
}
|
||||
// look up the job
|
||||
job := api.JobLookup(jobID)
|
||||
if job == nil {
|
||||
EncodeJSON(api.backend, w, r, SearchResult{Status: 2})
|
||||
return
|
||||
}
|
||||
|
||||
// upgrade to websocket
|
||||
conn, err := WSUpgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
// gorilla will automatically respond with "400 Bad Request", no other response is therefore necessary
|
||||
return
|
||||
}
|
||||
// upgrade to websocket
|
||||
conn, err := WSUpgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
// gorilla will automatically respond with "400 Bad Request", no other response is therefore necessary
|
||||
return
|
||||
}
|
||||
|
||||
defer conn.Close()
|
||||
defer conn.Close()
|
||||
|
||||
// loop to get new results and send out via the web socket.
|
||||
// Only exit if limit is reached if used, otherwise only if there are no result or the connection breaks.
|
||||
for {
|
||||
// query all results
|
||||
var resultFiles []*apiFile
|
||||
// loop to get new results and send out via the web socket.
|
||||
// Only exit if limit is reached if used, otherwise only if there are no result or the connection breaks.
|
||||
for {
|
||||
// query all results
|
||||
var resultFiles []*apiFile
|
||||
|
||||
queryCount := 1
|
||||
if useLimit {
|
||||
queryCount = limit
|
||||
}
|
||||
resultFiles = job.ReturnNext(queryCount)
|
||||
queryCount := 1
|
||||
if useLimit {
|
||||
queryCount = limit
|
||||
}
|
||||
resultFiles = job.ReturnNext(queryCount)
|
||||
|
||||
if useLimit {
|
||||
limit -= len(resultFiles)
|
||||
}
|
||||
if useLimit {
|
||||
limit -= len(resultFiles)
|
||||
}
|
||||
|
||||
// loop over results
|
||||
var result SearchResult
|
||||
result.Files = []apiFile{}
|
||||
// loop over results
|
||||
var result SearchResult
|
||||
result.Files = []apiFile{}
|
||||
|
||||
for n := range resultFiles {
|
||||
result.Files = append(result.Files, *resultFiles[n])
|
||||
}
|
||||
for n := range resultFiles {
|
||||
result.Files = append(result.Files, *resultFiles[n])
|
||||
}
|
||||
|
||||
if !job.IsSearchResults() {
|
||||
result.Status = 1 // No more results to expect
|
||||
if !job.IsSearchResults() {
|
||||
result.Status = 1 // No more results to expect
|
||||
|
||||
if len(result.Files) == 0 {
|
||||
conn.WriteJSON(result) // final message
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(result.Files) == 0 {
|
||||
conn.WriteJSON(result) // final message
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// if no results, stall
|
||||
if len(result.Files) == 0 {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
continue
|
||||
}
|
||||
// if no results, stall
|
||||
if len(result.Files) == 0 {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
continue
|
||||
}
|
||||
|
||||
// send out the results via the websocket
|
||||
if err := conn.WriteJSON(result); err != nil {
|
||||
return
|
||||
}
|
||||
// send out the results via the websocket
|
||||
if err := conn.WriteJSON(result); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Check whether to continue. If the limit is used break once all done.
|
||||
if (useLimit && limit <= 0) || result.Status == 1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Check whether to continue. If the limit is used break once all done.
|
||||
if (useLimit && limit <= 0) || result.Status == 1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -298,25 +298,25 @@ Response: 204 Empty
|
||||
*/
|
||||
func (api *WebapiInstance) apiSearchTerminate(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
r.ParseForm()
|
||||
jobID, err := uuid.Parse(r.Form.Get("id"))
|
||||
if err != nil {
|
||||
http.Error(w, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
r.ParseForm()
|
||||
jobID, err := uuid.Parse(r.Form.Get("id"))
|
||||
if err != nil {
|
||||
http.Error(w, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// look up the job
|
||||
job := api.JobLookup(jobID)
|
||||
if job == nil {
|
||||
http.Error(w, "", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
// look up the job
|
||||
job := api.JobLookup(jobID)
|
||||
if job == nil {
|
||||
http.Error(w, "", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// terminate and remove it from the list
|
||||
job.Terminate()
|
||||
api.RemoveJob(job)
|
||||
// terminate and remove it from the list
|
||||
job.Terminate()
|
||||
api.RemoveJob(job)
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -326,92 +326,95 @@ Request: GET /search/result?id=[UUID]
|
||||
Result: 200 with JSON structure SearchStatistic. Check the field status (0 = Success, 2 = ID not found).
|
||||
*/
|
||||
func (api *WebapiInstance) apiSearchStatistic(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
jobID, err := uuid.Parse(r.Form.Get("id"))
|
||||
if err != nil {
|
||||
http.Error(w, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
r.ParseForm()
|
||||
jobID, err := uuid.Parse(r.Form.Get("id"))
|
||||
if err != nil {
|
||||
http.Error(w, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// find the job ID
|
||||
job := api.JobLookup(jobID)
|
||||
if job == nil {
|
||||
EncodeJSON(api.backend, w, r, SearchStatistic{Status: 2})
|
||||
return
|
||||
}
|
||||
// find the job ID
|
||||
job := api.JobLookup(jobID)
|
||||
if job == nil {
|
||||
EncodeJSON(api.backend, w, r, SearchStatistic{Status: 2})
|
||||
return
|
||||
}
|
||||
|
||||
stats := job.Statistics()
|
||||
stats := job.Statistics()
|
||||
|
||||
EncodeJSON(api.backend, w, r, SearchStatistic{SearchStatisticData: stats, Status: 0, IsTerminated: job.IsTerminated()})
|
||||
EncodeJSON(api.backend, w, r, SearchStatistic{SearchStatisticData: stats, Status: 0, IsTerminated: job.IsTerminated()})
|
||||
}
|
||||
|
||||
/*
|
||||
apiExplore returns recently shared files in Peernet. Results are returned in real-time. The file type is an optional filter. See TypeX.
|
||||
Special type -2 = Binary, Compressed, Container, Executable. This special type includes everything except Documents, Video, Audio, Ebooks, Picture, Text.
|
||||
|
||||
Request: GET /explore?limit=[max records]&type=[file type]&offset=[offset]
|
||||
Request: GET /explore?limit=[max records]&type=[file type]&offset=[offset]&nodeid=[node id]
|
||||
Result: 200 with JSON structure SearchResult. Check the field status.
|
||||
*/
|
||||
func (api *WebapiInstance) apiExplore(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
offset, _ := strconv.Atoi(r.Form.Get("offset"))
|
||||
limit, err := strconv.Atoi(r.Form.Get("limit"))
|
||||
if err != nil {
|
||||
limit = 100
|
||||
}
|
||||
fileType, err := strconv.Atoi(r.Form.Get("type"))
|
||||
if err != nil {
|
||||
fileType = -1
|
||||
}
|
||||
r.ParseForm()
|
||||
offset, _ := strconv.Atoi(r.Form.Get("offset"))
|
||||
limit, err := strconv.Atoi(r.Form.Get("limit"))
|
||||
if err != nil {
|
||||
limit = 100
|
||||
}
|
||||
// ID fields for results for a specific node ID.
|
||||
nodeid, _ := DecodeBlake3Hash(r.Form.Get("nodeid"))
|
||||
|
||||
resultFiles := api.queryRecentShared(api.backend, fileType, uint64(limit*20/100), uint64(offset), uint64(limit))
|
||||
fileType, err := strconv.Atoi(r.Form.Get("type"))
|
||||
if err != nil {
|
||||
fileType = -1
|
||||
}
|
||||
|
||||
var result SearchResult
|
||||
result.Files = []apiFile{}
|
||||
resultFiles := api.queryRecentShared(api.backend, fileType, uint64(limit*20/100), uint64(offset), uint64(limit), nodeid)
|
||||
|
||||
// loop over results
|
||||
for n := range resultFiles {
|
||||
result.Files = append(result.Files, blockRecordFileToAPI(resultFiles[n]))
|
||||
}
|
||||
var result SearchResult
|
||||
result.Files = []apiFile{}
|
||||
|
||||
if len(result.Files) == 0 {
|
||||
result.Status = 3 // No results yet available keep trying
|
||||
}
|
||||
// loop over results
|
||||
for n := range resultFiles {
|
||||
result.Files = append(result.Files, blockRecordFileToAPI(resultFiles[n]))
|
||||
}
|
||||
|
||||
result.Status = 1 // No more results to expect
|
||||
if len(result.Files) == 0 {
|
||||
result.Status = 3 // No results yet available keep trying
|
||||
}
|
||||
|
||||
EncodeJSON(api.backend, w, r, result)
|
||||
result.Status = 1 // No more results to expect
|
||||
|
||||
EncodeJSON(api.backend, w, r, result)
|
||||
}
|
||||
|
||||
func (input *SearchRequest) Parse() (Timeout time.Duration) {
|
||||
if input.Timeout == 0 {
|
||||
Timeout = time.Second * 20 // default timeout: 20 seconds
|
||||
} else {
|
||||
Timeout = time.Duration(input.Timeout) * time.Second
|
||||
}
|
||||
if input.Timeout == 0 {
|
||||
Timeout = time.Second * 20 // default timeout: 20 seconds
|
||||
} else {
|
||||
Timeout = time.Duration(input.Timeout) * time.Second
|
||||
}
|
||||
|
||||
return
|
||||
return
|
||||
}
|
||||
|
||||
// ToSearchFilter converts the user input to a valid search filter
|
||||
func (input *SearchRequest) ToSearchFilter() (output SearchFilter) {
|
||||
return inputToSearchFilter(input.Sort, input.FileType, input.FileFormat, input.DateFrom, input.DateTo, input.SizeMin, input.SizeMax)
|
||||
return inputToSearchFilter(input.Sort, input.FileType, input.FileFormat, input.DateFrom, input.DateTo, input.SizeMin, input.SizeMax)
|
||||
}
|
||||
|
||||
func inputToSearchFilter(Sort, FileType, FileFormat int, DateFrom, DateTo string, SizeMin, SizeMax int) (output SearchFilter) {
|
||||
output.Sort = Sort
|
||||
output.FileType = FileType
|
||||
output.FileFormat = FileFormat
|
||||
output.SizeMin = SizeMin
|
||||
output.SizeMax = SizeMax
|
||||
output.Sort = Sort
|
||||
output.FileType = FileType
|
||||
output.FileFormat = FileFormat
|
||||
output.SizeMin = SizeMin
|
||||
output.SizeMax = SizeMax
|
||||
|
||||
dateFrom, errFrom := time.Parse(apiDateFormat, DateFrom)
|
||||
dateTo, errTo := time.Parse(apiDateFormat, DateTo)
|
||||
if errFrom == nil && errTo == nil && dateFrom.Before(dateTo) {
|
||||
output.DateFrom = dateFrom
|
||||
output.DateTo = dateTo
|
||||
output.IsDates = true
|
||||
}
|
||||
dateFrom, errFrom := time.Parse(apiDateFormat, DateFrom)
|
||||
dateTo, errTo := time.Parse(apiDateFormat, DateTo)
|
||||
if errFrom == nil && errTo == nil && dateFrom.Before(dateTo) {
|
||||
output.DateFrom = dateFrom
|
||||
output.DateTo = dateTo
|
||||
output.IsDates = true
|
||||
}
|
||||
|
||||
return
|
||||
return
|
||||
}
|
||||
|
||||
@@ -6,84 +6,93 @@ Author: Peter Kleissner
|
||||
package webapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core"
|
||||
"github.com/PeernetOfficial/core/blockchain"
|
||||
"github.com/PeernetOfficial/core"
|
||||
"github.com/PeernetOfficial/core/blockchain"
|
||||
)
|
||||
|
||||
// queryRecentShared returns recently shared files on the network from random peers until the limit is reached.
|
||||
func (api *WebapiInstance) queryRecentShared(backend *core.Backend, fileType int, limitPeer, offsetTotal, limitTotal uint64) (files []blockchain.BlockRecordFile) {
|
||||
if limitPeer == 0 {
|
||||
limitPeer = 1
|
||||
}
|
||||
func (api *WebapiInstance) queryRecentShared(backend *core.Backend, fileType int, limitPeer, offsetTotal, limitTotal uint64, nodeID []byte) (files []blockchain.BlockRecordFile) {
|
||||
if limitPeer == 0 {
|
||||
limitPeer = 1
|
||||
}
|
||||
|
||||
// Use the peer list to know about active peers. Random order!
|
||||
peerList := api.backend.PeerlistGet()
|
||||
// Assign peer list as an empty array
|
||||
var peerList []*core.PeerInfo
|
||||
|
||||
// Files from peers exceeding the limit. It is used if from all peers the total limit is not reached.
|
||||
var filesSeconday []blockchain.BlockRecordFile
|
||||
// check if the NodeID is provided or not
|
||||
if len(nodeID) == 0 {
|
||||
// Use the peer list to know about active peers. Random order!
|
||||
peerList = api.backend.PeerlistGet()
|
||||
} else {
|
||||
_, peerList[0], _ = api.backend.FindNode(nodeID, time.Second*5)
|
||||
}
|
||||
|
||||
for _, peer := range peerList {
|
||||
if peer.BlockchainHeight == 0 {
|
||||
continue
|
||||
}
|
||||
// Files from peers exceeding the limit. It is used if from all peers the total limit is not reached.
|
||||
var filesSeconday []blockchain.BlockRecordFile
|
||||
|
||||
var filesFromPeer uint64
|
||||
for _, peer := range peerList {
|
||||
if peer.BlockchainHeight == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// decode blocks from top down
|
||||
blockLoop:
|
||||
for blockN := peer.BlockchainHeight - 1; blockN > 0; blockN-- {
|
||||
blockDecoded, _, found, _ := backend.ReadBlock(peer.PublicKey, peer.BlockchainVersion, blockN)
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
var filesFromPeer uint64
|
||||
|
||||
for _, record := range blockDecoded.RecordsDecoded {
|
||||
if file, ok := record.(blockchain.BlockRecordFile); ok && isFileTypeMatchBlock(&file, fileType) {
|
||||
// add the tags 'Shared By Count' and 'Shared By GeoIP'
|
||||
file.Tags = append(file.Tags, blockchain.TagFromNumber(blockchain.TagSharedByCount, 1))
|
||||
if latitude, longitude, valid := api.Peer2GeoIP(peer); valid {
|
||||
sharedByGeoIP := fmt.Sprintf("%.4f", latitude) + "," + fmt.Sprintf("%.4f", longitude)
|
||||
file.Tags = append(file.Tags, blockchain.TagFromText(blockchain.TagSharedByGeoIP, sharedByGeoIP))
|
||||
}
|
||||
// decode blocks from top down
|
||||
blockLoop:
|
||||
for blockN := peer.BlockchainHeight - 1; blockN > 0; blockN-- {
|
||||
blockDecoded, _, found, _ := backend.ReadBlock(peer.PublicKey, peer.BlockchainVersion, blockN)
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
||||
// found a new file! append.
|
||||
if filesFromPeer < limitPeer {
|
||||
filesFromPeer++
|
||||
for _, record := range blockDecoded.RecordsDecoded {
|
||||
if file, ok := record.(blockchain.BlockRecordFile); ok && isFileTypeMatchBlock(&file, fileType) {
|
||||
// add the tags 'Shared By Count' and 'Shared By GeoIP'
|
||||
file.Tags = append(file.Tags, blockchain.TagFromNumber(blockchain.TagSharedByCount, 1))
|
||||
if latitude, longitude, valid := api.Peer2GeoIP(peer); valid {
|
||||
sharedByGeoIP := fmt.Sprintf("%.4f", latitude) + "," + fmt.Sprintf("%.4f", longitude)
|
||||
file.Tags = append(file.Tags, blockchain.TagFromText(blockchain.TagSharedByGeoIP, sharedByGeoIP))
|
||||
}
|
||||
|
||||
if offsetTotal > 0 {
|
||||
offsetTotal--
|
||||
continue
|
||||
}
|
||||
// found a new file! append.
|
||||
if filesFromPeer < limitPeer {
|
||||
filesFromPeer++
|
||||
|
||||
files = append(files, file)
|
||||
if offsetTotal > 0 {
|
||||
offsetTotal--
|
||||
continue
|
||||
}
|
||||
|
||||
if uint64(len(files)) >= limitTotal {
|
||||
return
|
||||
}
|
||||
} else if uint64(len(filesSeconday)) < limitTotal-uint64(len(files)) {
|
||||
filesSeconday = append(filesSeconday, file)
|
||||
} else {
|
||||
break blockLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
files = append(files, file)
|
||||
|
||||
files = append(files, filesSeconday...)
|
||||
if uint64(len(files)) >= limitTotal {
|
||||
return
|
||||
}
|
||||
} else if uint64(len(filesSeconday)) < limitTotal-uint64(len(files)) {
|
||||
filesSeconday = append(filesSeconday, file)
|
||||
} else {
|
||||
break blockLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
files = append(files, filesSeconday...)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// isFileTypeMatchBlock checks if the file type matches. -1 = accept any. -2 = core.TypeBinary, core.TypeCompressed, core.TypeContainer, core.TypeExecutable.
|
||||
func isFileTypeMatchBlock(file *blockchain.BlockRecordFile, fileType int) bool {
|
||||
if fileType == -1 {
|
||||
return true
|
||||
} else if fileType == -2 {
|
||||
return file.Type == core.TypeBinary || file.Type == core.TypeCompressed || file.Type == core.TypeContainer || file.Type == core.TypeExecutable
|
||||
}
|
||||
if fileType == -1 {
|
||||
return true
|
||||
} else if fileType == -2 {
|
||||
return file.Type == core.TypeBinary || file.Type == core.TypeCompressed || file.Type == core.TypeContainer || file.Type == core.TypeExecutable
|
||||
}
|
||||
|
||||
return file.Type == uint8(fileType)
|
||||
return file.Type == uint8(fileType)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user