gofmt peernet (#103)

This commit is contained in:
Akilan Selvacoumar
2023-02-12 09:59:31 +00:00
committed by GitHub
parent 3a1eece580
commit 9775525d35
95 changed files with 15798 additions and 15794 deletions

View File

@@ -125,27 +125,30 @@ var (
// the arithmetic needed for elliptic curve operations.
//
// The following depicts the internal representation:
// -----------------------------------------------------------------
// | n[9] | n[8] | ... | n[0] |
// | 32 bits available | 32 bits available | ... | 32 bits available |
// | 22 bits for value | 26 bits for value | ... | 26 bits for value |
// | 10 bits overflow | 6 bits overflow | ... | 6 bits overflow |
// | Mult: 2^(26*9) | Mult: 2^(26*8) | ... | Mult: 2^(26*0) |
// -----------------------------------------------------------------
//
// -----------------------------------------------------------------
// | n[9] | n[8] | ... | n[0] |
// | 32 bits available | 32 bits available | ... | 32 bits available |
// | 22 bits for value | 26 bits for value | ... | 26 bits for value |
// | 10 bits overflow | 6 bits overflow | ... | 6 bits overflow |
// | Mult: 2^(26*9) | Mult: 2^(26*8) | ... | Mult: 2^(26*0) |
// -----------------------------------------------------------------
//
// For example, consider the number 2^49 + 1. It would be represented as:
// n[0] = 1
// n[1] = 2^23
// n[2..9] = 0
//
// n[0] = 1
// n[1] = 2^23
// n[2..9] = 0
//
// The full 256-bit value is then calculated by looping i from 9..0 and
// doing sum(n[i] * 2^(26i)) like so:
// n[9] * 2^(26*9) = 0 * 2^234 = 0
// n[8] * 2^(26*8) = 0 * 2^208 = 0
// ...
// n[1] * 2^(26*1) = 2^23 * 2^26 = 2^49
// n[0] * 2^(26*0) = 1 * 2^0 = 1
// Sum: 0 + 0 + ... + 2^49 + 1 = 2^49 + 1
//
// n[9] * 2^(26*9) = 0 * 2^234 = 0
// n[8] * 2^(26*8) = 0 * 2^208 = 0
// ...
// n[1] * 2^(26*1) = 2^23 * 2^26 = 2^49
// n[0] * 2^(26*0) = 1 * 2^0 = 1
// Sum: 0 + 0 + ... + 2^49 + 1 = 2^49 + 1
type fieldVal struct {
n [10]uint32
}

View File

@@ -4,6 +4,7 @@
// This file is ignored during the regular build due to the following build tag.
// This build tag is set during go generate.
//go:build gensecp256k1
// +build gensecp256k1
package btcec

View File

@@ -306,7 +306,7 @@ func (ht *hashTable) getTotalNodesPerBucket() (total []int) {
ht.mutex.RLock()
defer ht.mutex.RUnlock()
for n, _ := range ht.RoutingTable {
for n := range ht.RoutingTable {
total = append(total, len(ht.RoutingTable[n]))
}

View File

@@ -4,14 +4,14 @@
//
// For example:
//
// // listen on the same port. oh yeah.
// l1, _ := reuse.Listen("tcp", "127.0.0.1:1234")
// l2, _ := reuse.Listen("tcp", "127.0.0.1:1234")
// // listen on the same port. oh yeah.
// l1, _ := reuse.Listen("tcp", "127.0.0.1:1234")
// l2, _ := reuse.Listen("tcp", "127.0.0.1:1234")
//
// // dial from the same port. oh yeah.
// l1, _ := reuse.Listen("tcp", "127.0.0.1:1234")
// l2, _ := reuse.Listen("tcp", "127.0.0.1:1235")
// c, _ := reuse.Dial("tcp", "127.0.0.1:1234", "127.0.0.1:1235")
// // dial from the same port. oh yeah.
// l1, _ := reuse.Listen("tcp", "127.0.0.1:1234")
// l2, _ := reuse.Listen("tcp", "127.0.0.1:1235")
// c, _ := reuse.Dial("tcp", "127.0.0.1:1234", "127.0.0.1:1235")
//
// Note: cant dial self because tcp/ip stacks use 4-tuples to identify connections,
// and doing so would clash.

View File

@@ -7,192 +7,192 @@ Author: Peter Kleissner
package webapi
import (
"crypto/tls"
"encoding/json"
"errors"
"net/http"
"sync"
"time"
"crypto/tls"
"encoding/json"
"errors"
"net/http"
"sync"
"time"
"github.com/IncSW/geoip2"
"github.com/PeernetOfficial/core"
"github.com/google/uuid"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"github.com/IncSW/geoip2"
"github.com/PeernetOfficial/core"
"github.com/google/uuid"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
)
type WebapiInstance struct {
backend *core.Backend
geoipCityReader *geoip2.CityReader
backend *core.Backend
geoipCityReader *geoip2.CityReader
// Router can be used to register additional API functions
Router *mux.Router
AllowKeyInParam []string // List of paths that accept the API key as &k= parameter
// Router can be used to register additional API functions
Router *mux.Router
AllowKeyInParam []string // List of paths that accept the API key as &k= parameter
// search jobs
allJobs map[uuid.UUID]*SearchJob
allJobsMutex sync.RWMutex
// search jobs
allJobs map[uuid.UUID]*SearchJob
allJobsMutex sync.RWMutex
// download info
downloads map[uuid.UUID]*downloadInfo
downloadsMutex sync.RWMutex
// download info
downloads map[uuid.UUID]*downloadInfo
downloadsMutex sync.RWMutex
}
// WSUpgrader is used for websocket functionality. It allows all requests.
var WSUpgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
// allow all connections by default
return true
},
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
// allow all connections by default
return true
},
}
// Start starts the API. ListenAddresses is a list of IP:Ports.
// The certificate file and key are only used if SSL is enabled. The read and write timeout may be 0 for no timeout.
// The API key may be uuid.Nil to disable it although this is not recommended for security reasons.
func Start(Backend *core.Backend, ListenAddresses []string, UseSSL bool, CertificateFile, CertificateKey string, TimeoutRead, TimeoutWrite time.Duration, APIKey uuid.UUID) (api *WebapiInstance) {
if len(ListenAddresses) == 0 {
return nil
}
if len(ListenAddresses) == 0 {
return nil
}
api = &WebapiInstance{
backend: Backend,
Router: mux.NewRouter(),
AllowKeyInParam: []string{"/file/read", "/file/view"},
allJobs: make(map[uuid.UUID]*SearchJob),
downloads: make(map[uuid.UUID]*downloadInfo),
}
api = &WebapiInstance{
backend: Backend,
Router: mux.NewRouter(),
AllowKeyInParam: []string{"/file/read", "/file/view"},
allJobs: make(map[uuid.UUID]*SearchJob),
downloads: make(map[uuid.UUID]*downloadInfo),
}
if APIKey != uuid.Nil {
api.Router.Use(api.authenticateMiddleware(APIKey))
}
if APIKey != uuid.Nil {
api.Router.Use(api.authenticateMiddleware(APIKey))
}
api.Router.HandleFunc("/test", apiTest).Methods("GET")
api.Router.HandleFunc("/status", api.apiStatus).Methods("GET")
api.Router.HandleFunc("/status/peers", api.apiStatusPeers).Methods("GET")
api.Router.HandleFunc("/account/info", api.apiAccountInfo).Methods("GET")
api.Router.HandleFunc("/account/delete", api.apiAccountDelete).Methods("GET")
api.Router.HandleFunc("/blockchain/header", api.apiBlockchainHeaderFunc).Methods("GET")
api.Router.HandleFunc("/blockchain/append", api.apiBlockchainAppend).Methods("POST")
api.Router.HandleFunc("/blockchain/read", api.apiBlockchainRead).Methods("GET")
api.Router.HandleFunc("/blockchain/file/add", api.apiBlockchainFileAdd).Methods("POST")
api.Router.HandleFunc("/blockchain/file/list", api.apiBlockchainFileList).Methods("GET")
api.Router.HandleFunc("/blockchain/file/delete", api.apiBlockchainFileDelete).Methods("POST")
api.Router.HandleFunc("/blockchain/file/update", api.apiBlockchainFileUpdate).Methods("POST")
api.Router.HandleFunc("/profile/list", api.apiProfileList).Methods("GET")
api.Router.HandleFunc("/profile/read", api.apiProfileRead).Methods("GET")
api.Router.HandleFunc("/profile/write", api.apiProfileWrite).Methods("POST")
api.Router.HandleFunc("/profile/delete", api.apiProfileDelete).Methods("POST")
api.Router.HandleFunc("/search", api.apiSearch).Methods("POST")
api.Router.HandleFunc("/search/result", api.apiSearchResult).Methods("GET")
api.Router.HandleFunc("/search/result/ws", api.apiSearchResultStream).Methods("GET")
api.Router.HandleFunc("/search/statistic", api.apiSearchStatistic).Methods("GET")
api.Router.HandleFunc("/search/terminate", api.apiSearchTerminate).Methods("GET")
api.Router.HandleFunc("/explore", api.apiExplore).Methods("GET")
api.Router.HandleFunc("/explore/node", api.apiExploreNodeID).Methods("GET")
api.Router.HandleFunc("/file/format", api.apiFileFormat).Methods("GET")
api.Router.HandleFunc("/download/start", api.apiDownloadStart).Methods("GET")
api.Router.HandleFunc("/download/status", api.apiDownloadStatus).Methods("GET")
api.Router.HandleFunc("/download/action", api.apiDownloadAction).Methods("GET")
api.Router.HandleFunc("/warehouse/create", api.apiWarehouseCreateFile).Methods("POST")
api.Router.HandleFunc("/warehouse/create/path", api.apiWarehouseCreateFilePath).Methods("GET")
api.Router.HandleFunc("/warehouse/read", api.apiWarehouseReadFile).Methods("GET")
api.Router.HandleFunc("/warehouse/read/path", api.apiWarehouseReadFilePath).Methods("GET")
api.Router.HandleFunc("/warehouse/delete", api.apiWarehouseDeleteFile).Methods("GET")
api.Router.HandleFunc("/file/read", api.apiFileRead).Methods("GET")
api.Router.HandleFunc("/file/view", api.apiFileView).Methods("GET")
api.Router.HandleFunc("/test", apiTest).Methods("GET")
api.Router.HandleFunc("/status", api.apiStatus).Methods("GET")
api.Router.HandleFunc("/status/peers", api.apiStatusPeers).Methods("GET")
api.Router.HandleFunc("/account/info", api.apiAccountInfo).Methods("GET")
api.Router.HandleFunc("/account/delete", api.apiAccountDelete).Methods("GET")
api.Router.HandleFunc("/blockchain/header", api.apiBlockchainHeaderFunc).Methods("GET")
api.Router.HandleFunc("/blockchain/append", api.apiBlockchainAppend).Methods("POST")
api.Router.HandleFunc("/blockchain/read", api.apiBlockchainRead).Methods("GET")
api.Router.HandleFunc("/blockchain/file/add", api.apiBlockchainFileAdd).Methods("POST")
api.Router.HandleFunc("/blockchain/file/list", api.apiBlockchainFileList).Methods("GET")
api.Router.HandleFunc("/blockchain/file/delete", api.apiBlockchainFileDelete).Methods("POST")
api.Router.HandleFunc("/blockchain/file/update", api.apiBlockchainFileUpdate).Methods("POST")
api.Router.HandleFunc("/profile/list", api.apiProfileList).Methods("GET")
api.Router.HandleFunc("/profile/read", api.apiProfileRead).Methods("GET")
api.Router.HandleFunc("/profile/write", api.apiProfileWrite).Methods("POST")
api.Router.HandleFunc("/profile/delete", api.apiProfileDelete).Methods("POST")
api.Router.HandleFunc("/search", api.apiSearch).Methods("POST")
api.Router.HandleFunc("/search/result", api.apiSearchResult).Methods("GET")
api.Router.HandleFunc("/search/result/ws", api.apiSearchResultStream).Methods("GET")
api.Router.HandleFunc("/search/statistic", api.apiSearchStatistic).Methods("GET")
api.Router.HandleFunc("/search/terminate", api.apiSearchTerminate).Methods("GET")
api.Router.HandleFunc("/explore", api.apiExplore).Methods("GET")
api.Router.HandleFunc("/explore/node", api.apiExploreNodeID).Methods("GET")
api.Router.HandleFunc("/file/format", api.apiFileFormat).Methods("GET")
api.Router.HandleFunc("/download/start", api.apiDownloadStart).Methods("GET")
api.Router.HandleFunc("/download/status", api.apiDownloadStatus).Methods("GET")
api.Router.HandleFunc("/download/action", api.apiDownloadAction).Methods("GET")
api.Router.HandleFunc("/warehouse/create", api.apiWarehouseCreateFile).Methods("POST")
api.Router.HandleFunc("/warehouse/create/path", api.apiWarehouseCreateFilePath).Methods("GET")
api.Router.HandleFunc("/warehouse/read", api.apiWarehouseReadFile).Methods("GET")
api.Router.HandleFunc("/warehouse/read/path", api.apiWarehouseReadFilePath).Methods("GET")
api.Router.HandleFunc("/warehouse/delete", api.apiWarehouseDeleteFile).Methods("GET")
api.Router.HandleFunc("/file/read", api.apiFileRead).Methods("GET")
api.Router.HandleFunc("/file/view", api.apiFileView).Methods("GET")
for _, listen := range ListenAddresses {
go startWebAPI(Backend, listen, UseSSL, CertificateFile, CertificateKey, api.Router, "API", TimeoutRead, TimeoutWrite)
}
for _, listen := range ListenAddresses {
go startWebAPI(Backend, listen, UseSSL, CertificateFile, CertificateKey, api.Router, "API", TimeoutRead, TimeoutWrite)
}
return api
return api
}
// startWebAPI starts a web-server with given parameters and logs the status. If may block forever and only returns if there is an error.
// The certificate file and key are only used if SSL is enabled. The read and write timeout may be 0 for no timeout.
func startWebAPI(Backend *core.Backend, WebListen string, UseSSL bool, CertificateFile, CertificateKey string, Handler http.Handler, Info string, ReadTimeout, WriteTimeout time.Duration) {
Backend.LogError("startWebAPI", "Start API at '%s'\n", WebListen)
Backend.LogError("startWebAPI", "Start API at '%s'\n", WebListen)
tlsConfig := &tls.Config{MinVersion: tls.VersionTLS12} // for security reasons disable TLS 1.0/1.1
tlsConfig := &tls.Config{MinVersion: tls.VersionTLS12} // for security reasons disable TLS 1.0/1.1
server := &http.Server{
Addr: WebListen,
Handler: Handler,
ReadTimeout: ReadTimeout, // ReadTimeout is the maximum duration for reading the entire request, including the body.
WriteTimeout: WriteTimeout, // WriteTimeout is the maximum duration before timing out writes of the response. This includes processing time and is therefore the max time any HTTP function may take.
//IdleTimeout: IdleTimeout, // IdleTimeout is the maximum amount of time to wait for the next request when keep-alives are enabled.
TLSConfig: tlsConfig,
}
server := &http.Server{
Addr: WebListen,
Handler: Handler,
ReadTimeout: ReadTimeout, // ReadTimeout is the maximum duration for reading the entire request, including the body.
WriteTimeout: WriteTimeout, // WriteTimeout is the maximum duration before timing out writes of the response. This includes processing time and is therefore the max time any HTTP function may take.
//IdleTimeout: IdleTimeout, // IdleTimeout is the maximum amount of time to wait for the next request when keep-alives are enabled.
TLSConfig: tlsConfig,
}
if UseSSL {
// HTTPS
if err := server.ListenAndServeTLS(CertificateFile, CertificateKey); err != nil {
Backend.LogError("startWebAPI", "Error listening on '%s': %v\n", WebListen, err)
}
} else {
// HTTP
if err := server.ListenAndServe(); err != nil {
Backend.LogError("startWebAPI", "Error listening on '%s': %v\n", WebListen, err)
}
}
if UseSSL {
// HTTPS
if err := server.ListenAndServeTLS(CertificateFile, CertificateKey); err != nil {
Backend.LogError("startWebAPI", "Error listening on '%s': %v\n", WebListen, err)
}
} else {
// HTTP
if err := server.ListenAndServe(); err != nil {
Backend.LogError("startWebAPI", "Error listening on '%s': %v\n", WebListen, err)
}
}
}
// EncodeJSON encodes the data as JSON
func EncodeJSON(Backend *core.Backend, w http.ResponseWriter, r *http.Request, data interface{}) (err error) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(data)
if err != nil {
Backend.LogError("EncodeJSON", "Error writing data for route '%s': %v\n", r.URL.Path, err)
}
err = json.NewEncoder(w).Encode(data)
if err != nil {
Backend.LogError("EncodeJSON", "Error writing data for route '%s': %v\n", r.URL.Path, err)
}
return err
return err
}
// DecodeJSON decodes input JSON data server side sent either via GET or POST. It does not limit the maximum amount to read.
// In case of error it will automatically send an error to the client.
func DecodeJSON(w http.ResponseWriter, r *http.Request, data interface{}) (err error) {
if r.Body == nil {
http.Error(w, "", http.StatusBadRequest)
return errors.New("no data")
}
if r.Body == nil {
http.Error(w, "", http.StatusBadRequest)
return errors.New("no data")
}
err = json.NewDecoder(r.Body).Decode(data)
if err != nil {
http.Error(w, "", http.StatusBadRequest)
return err
}
err = json.NewDecoder(r.Body).Decode(data)
if err != nil {
http.Error(w, "", http.StatusBadRequest)
return err
}
return nil
return nil
}
// authenticateMiddleware returns a middleware function to be used with mux.Router.Use(). It handles all authentication functionality.
func (api *WebapiInstance) authenticateMiddleware(APIKey uuid.UUID) func(http.Handler) http.Handler {
return (func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
keyID, err := uuid.Parse(r.Header.Get("x-api-key"))
if err != nil { // special case for some paths
for _, exceptPath := range api.AllowKeyInParam {
if exceptPath == r.URL.Path {
r.ParseForm()
keyID, err = uuid.Parse(r.Form.Get("k"))
break
}
}
}
if err != nil { // Invalid key format
w.WriteHeader(http.StatusUnauthorized)
return
}
return (func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
keyID, err := uuid.Parse(r.Header.Get("x-api-key"))
if err != nil { // special case for some paths
for _, exceptPath := range api.AllowKeyInParam {
if exceptPath == r.URL.Path {
r.ParseForm()
keyID, err = uuid.Parse(r.Form.Get("k"))
break
}
}
}
if err != nil { // Invalid key format
w.WriteHeader(http.StatusUnauthorized)
return
}
if keyID != APIKey {
w.WriteHeader(http.StatusUnauthorized)
return
}
if keyID != APIKey {
w.WriteHeader(http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
})
next.ServeHTTP(w, r)
})
})
}

View File

@@ -1,13 +1,13 @@
package webapi
import (
"fmt"
"testing"
"fmt"
"testing"
)
// Test function
func TestDecodeBlake3Hash(t *testing.T) {
hash, bool := DecodeBlake3Hash("")
fmt.Println(hash)
fmt.Println(bool)
hash, bool := DecodeBlake3Hash("")
fmt.Println(hash)
fmt.Println(bool)
}

View File

@@ -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
400 on invalid JSON
*/
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})
}
/*
@@ -128,82 +128,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)
}
/*
@@ -215,80 +215,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
}
}
}
/*
@@ -302,25 +302,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)
}
/*
@@ -330,23 +330,23 @@ 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()})
}
/*
@@ -357,21 +357,21 @@ Request: GET /explore?limit=[max records]&type=[file type]&offset=[offset]
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
}
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
}
fileType, err := strconv.Atoi(r.Form.Get("type"))
if err != nil {
fileType = -1
}
result := api.ExploreHelper(fileType,limit,offset,[]byte{},false)
result := api.ExploreHelper(fileType, limit, offset, []byte{}, false)
EncodeJSON(api.backend, w, r, result)
EncodeJSON(api.backend, w, r, result)
}
/*
@@ -382,75 +382,75 @@ Request: GET /explore/node?limit=[max records]&type=[file type]&offset=[offse
Result: 200 with JSON structure SearchResult. Check the field status.
*/
func (api *WebapiInstance) apiExploreNodeID(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
}
// ID fields for results for a specific node ID.
NodeId, _ := DecodeBlake3Hash(r.Form.Get("NodeID"))
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"))
fileType, err := strconv.Atoi(r.Form.Get("type"))
if err != nil {
fileType = -1
}
fileType, err := strconv.Atoi(r.Form.Get("type"))
if err != nil {
fileType = -1
}
result := api.ExploreHelper(fileType,limit,offset,NodeId,true)
result := api.ExploreHelper(fileType, limit, offset, NodeId, true)
EncodeJSON(api.backend, w, r, result)
EncodeJSON(api.backend, w, r, result)
}
// ExploreHelper Helper function for the explore route with the possibility search based on a node ID
func (api *WebapiInstance) ExploreHelper(fileType int, limit, offset int, nodeID []byte, nodeIDState bool) *SearchResult{
resultFiles := api.queryRecentShared(api.backend, fileType, uint64(limit*20/100), uint64(offset), uint64(limit), nodeID, nodeIDState)
func (api *WebapiInstance) ExploreHelper(fileType int, limit, offset int, nodeID []byte, nodeIDState bool) *SearchResult {
resultFiles := api.queryRecentShared(api.backend, fileType, uint64(limit*20/100), uint64(offset), uint64(limit), nodeID, nodeIDState)
var result SearchResult
result.Files = []apiFile{}
var result SearchResult
result.Files = []apiFile{}
// loop over results
for n := range resultFiles {
result.Files = append(result.Files, blockRecordFileToAPI(resultFiles[n]))
}
// loop over results
for n := range resultFiles {
result.Files = append(result.Files, blockRecordFileToAPI(resultFiles[n]))
}
if len(result.Files) == 0 {
result.Status = 3 // No results yet available keep trying
}
if len(result.Files) == 0 {
result.Status = 3 // No results yet available keep trying
}
result.Status = 1 // No more results to expect
result.Status = 1 // No more results to expect
return &result
return &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
}

View File

@@ -6,98 +6,98 @@ Author: Peter Kleissner
package webapi
import (
"fmt"
"time"
"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, nodeID []byte, nodeIDState bool) (files []blockchain.BlockRecordFile) {
if limitPeer == 0 {
limitPeer = 1
}
if limitPeer == 0 {
limitPeer = 1
}
// Assign peer list as an empty array
var peerList []*core.PeerInfo
// Assign peer list as an empty array
var peerList []*core.PeerInfo
// check if the NodeID is provided or not
if len(nodeID) == 0 && !nodeIDState {
// Use the peer list to know about active peers. Random order!
peerList = api.backend.PeerlistGet()
} else {
// Get peer information based on the NodeID provided
_, peer, err := api.backend.FindNode(nodeID, time.Second*5)
if err != nil {
return
}
peerList = append(peerList, peer)
}
// check if the NodeID is provided or not
if len(nodeID) == 0 && !nodeIDState {
// Use the peer list to know about active peers. Random order!
peerList = api.backend.PeerlistGet()
} else {
// Get peer information based on the NodeID provided
_, peer, err := api.backend.FindNode(nodeID, time.Second*5)
if err != nil {
return
}
peerList = append(peerList, peer)
}
// Files from peers exceeding the limit. It is used if from all peers the total limit is not reached.
var filesSeconday []blockchain.BlockRecordFile
// Files from peers exceeding the limit. It is used if from all peers the total limit is not reached.
var filesSeconday []blockchain.BlockRecordFile
for _, peer := range peerList {
if peer.BlockchainHeight == 0 {
continue
}
for _, peer := range peerList {
if peer.BlockchainHeight == 0 {
continue
}
var filesFromPeer uint64
var filesFromPeer uint64
// 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
}
// 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
}
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))
}
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))
}
// found a new file! append.
if filesFromPeer < limitPeer {
filesFromPeer++
// found a new file! append.
if filesFromPeer < limitPeer {
filesFromPeer++
if offsetTotal > 0 {
offsetTotal--
continue
}
if offsetTotal > 0 {
offsetTotal--
continue
}
files = append(files, file)
files = append(files, file)
if uint64(len(files)) >= limitTotal {
return
}
} else if uint64(len(filesSeconday)) < limitTotal-uint64(len(files)) {
filesSeconday = append(filesSeconday, file)
} else {
break blockLoop
}
}
}
}
}
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, filesSeconday...)
files = append(files, filesSeconday...)
return
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)
}