mirror of
https://github.com/PeernetOfficial/core.git
synced 2026-07-17 02:47:51 +01:00
gofmt peernet (#103)
This commit is contained in:
committed by
GitHub
parent
3a1eece580
commit
9775525d35
@@ -125,27 +125,30 @@ var (
|
|||||||
// the arithmetic needed for elliptic curve operations.
|
// the arithmetic needed for elliptic curve operations.
|
||||||
//
|
//
|
||||||
// The following depicts the internal representation:
|
// The following depicts the internal representation:
|
||||||
// -----------------------------------------------------------------
|
//
|
||||||
// | n[9] | n[8] | ... | n[0] |
|
// -----------------------------------------------------------------
|
||||||
// | 32 bits available | 32 bits available | ... | 32 bits available |
|
// | n[9] | n[8] | ... | n[0] |
|
||||||
// | 22 bits for value | 26 bits for value | ... | 26 bits for value |
|
// | 32 bits available | 32 bits available | ... | 32 bits available |
|
||||||
// | 10 bits overflow | 6 bits overflow | ... | 6 bits overflow |
|
// | 22 bits for value | 26 bits for value | ... | 26 bits for value |
|
||||||
// | Mult: 2^(26*9) | Mult: 2^(26*8) | ... | Mult: 2^(26*0) |
|
// | 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:
|
// For example, consider the number 2^49 + 1. It would be represented as:
|
||||||
// n[0] = 1
|
//
|
||||||
// n[1] = 2^23
|
// n[0] = 1
|
||||||
// n[2..9] = 0
|
// n[1] = 2^23
|
||||||
|
// n[2..9] = 0
|
||||||
//
|
//
|
||||||
// The full 256-bit value is then calculated by looping i from 9..0 and
|
// The full 256-bit value is then calculated by looping i from 9..0 and
|
||||||
// doing sum(n[i] * 2^(26i)) like so:
|
// 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[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
|
// n[1] * 2^(26*1) = 2^23 * 2^26 = 2^49
|
||||||
// Sum: 0 + 0 + ... + 2^49 + 1 = 2^49 + 1
|
// n[0] * 2^(26*0) = 1 * 2^0 = 1
|
||||||
|
// Sum: 0 + 0 + ... + 2^49 + 1 = 2^49 + 1
|
||||||
type fieldVal struct {
|
type fieldVal struct {
|
||||||
n [10]uint32
|
n [10]uint32
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
// This file is ignored during the regular build due to the following build tag.
|
// This file is ignored during the regular build due to the following build tag.
|
||||||
// This build tag is set during go generate.
|
// This build tag is set during go generate.
|
||||||
|
//go:build gensecp256k1
|
||||||
// +build gensecp256k1
|
// +build gensecp256k1
|
||||||
|
|
||||||
package btcec
|
package btcec
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ func (ht *hashTable) getTotalNodesPerBucket() (total []int) {
|
|||||||
ht.mutex.RLock()
|
ht.mutex.RLock()
|
||||||
defer ht.mutex.RUnlock()
|
defer ht.mutex.RUnlock()
|
||||||
|
|
||||||
for n, _ := range ht.RoutingTable {
|
for n := range ht.RoutingTable {
|
||||||
total = append(total, len(ht.RoutingTable[n]))
|
total = append(total, len(ht.RoutingTable[n]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,14 @@
|
|||||||
//
|
//
|
||||||
// For example:
|
// For example:
|
||||||
//
|
//
|
||||||
// // listen on the same port. oh yeah.
|
// // listen on the same port. oh yeah.
|
||||||
// l1, _ := reuse.Listen("tcp", "127.0.0.1:1234")
|
// l1, _ := reuse.Listen("tcp", "127.0.0.1:1234")
|
||||||
// l2, _ := reuse.Listen("tcp", "127.0.0.1:1234")
|
// l2, _ := reuse.Listen("tcp", "127.0.0.1:1234")
|
||||||
//
|
//
|
||||||
// // dial from the same port. oh yeah.
|
// // dial from the same port. oh yeah.
|
||||||
// l1, _ := reuse.Listen("tcp", "127.0.0.1:1234")
|
// l1, _ := reuse.Listen("tcp", "127.0.0.1:1234")
|
||||||
// l2, _ := reuse.Listen("tcp", "127.0.0.1:1235")
|
// l2, _ := reuse.Listen("tcp", "127.0.0.1:1235")
|
||||||
// c, _ := reuse.Dial("tcp", "127.0.0.1:1234", "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,
|
// Note: cant dial self because tcp/ip stacks use 4-tuples to identify connections,
|
||||||
// and doing so would clash.
|
// and doing so would clash.
|
||||||
|
|||||||
278
webapi/API.go
278
webapi/API.go
@@ -7,192 +7,192 @@ Author: Peter Kleissner
|
|||||||
package webapi
|
package webapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/IncSW/geoip2"
|
"github.com/IncSW/geoip2"
|
||||||
"github.com/PeernetOfficial/core"
|
"github.com/PeernetOfficial/core"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WebapiInstance struct {
|
type WebapiInstance struct {
|
||||||
backend *core.Backend
|
backend *core.Backend
|
||||||
geoipCityReader *geoip2.CityReader
|
geoipCityReader *geoip2.CityReader
|
||||||
|
|
||||||
// Router can be used to register additional API functions
|
// Router can be used to register additional API functions
|
||||||
Router *mux.Router
|
Router *mux.Router
|
||||||
AllowKeyInParam []string // List of paths that accept the API key as &k= parameter
|
AllowKeyInParam []string // List of paths that accept the API key as &k= parameter
|
||||||
|
|
||||||
// search jobs
|
// search jobs
|
||||||
allJobs map[uuid.UUID]*SearchJob
|
allJobs map[uuid.UUID]*SearchJob
|
||||||
allJobsMutex sync.RWMutex
|
allJobsMutex sync.RWMutex
|
||||||
|
|
||||||
// download info
|
// download info
|
||||||
downloads map[uuid.UUID]*downloadInfo
|
downloads map[uuid.UUID]*downloadInfo
|
||||||
downloadsMutex sync.RWMutex
|
downloadsMutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// WSUpgrader is used for websocket functionality. It allows all requests.
|
// WSUpgrader is used for websocket functionality. It allows all requests.
|
||||||
var WSUpgrader = websocket.Upgrader{
|
var WSUpgrader = websocket.Upgrader{
|
||||||
ReadBufferSize: 1024,
|
ReadBufferSize: 1024,
|
||||||
WriteBufferSize: 1024,
|
WriteBufferSize: 1024,
|
||||||
CheckOrigin: func(r *http.Request) bool {
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
// allow all connections by default
|
// allow all connections by default
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start starts the API. ListenAddresses is a list of IP:Ports.
|
// 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 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.
|
// 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) {
|
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 {
|
if len(ListenAddresses) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
api = &WebapiInstance{
|
api = &WebapiInstance{
|
||||||
backend: Backend,
|
backend: Backend,
|
||||||
Router: mux.NewRouter(),
|
Router: mux.NewRouter(),
|
||||||
AllowKeyInParam: []string{"/file/read", "/file/view"},
|
AllowKeyInParam: []string{"/file/read", "/file/view"},
|
||||||
allJobs: make(map[uuid.UUID]*SearchJob),
|
allJobs: make(map[uuid.UUID]*SearchJob),
|
||||||
downloads: make(map[uuid.UUID]*downloadInfo),
|
downloads: make(map[uuid.UUID]*downloadInfo),
|
||||||
}
|
}
|
||||||
|
|
||||||
if APIKey != uuid.Nil {
|
if APIKey != uuid.Nil {
|
||||||
api.Router.Use(api.authenticateMiddleware(APIKey))
|
api.Router.Use(api.authenticateMiddleware(APIKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
api.Router.HandleFunc("/test", apiTest).Methods("GET")
|
api.Router.HandleFunc("/test", apiTest).Methods("GET")
|
||||||
api.Router.HandleFunc("/status", api.apiStatus).Methods("GET")
|
api.Router.HandleFunc("/status", api.apiStatus).Methods("GET")
|
||||||
api.Router.HandleFunc("/status/peers", api.apiStatusPeers).Methods("GET")
|
api.Router.HandleFunc("/status/peers", api.apiStatusPeers).Methods("GET")
|
||||||
api.Router.HandleFunc("/account/info", api.apiAccountInfo).Methods("GET")
|
api.Router.HandleFunc("/account/info", api.apiAccountInfo).Methods("GET")
|
||||||
api.Router.HandleFunc("/account/delete", api.apiAccountDelete).Methods("GET")
|
api.Router.HandleFunc("/account/delete", api.apiAccountDelete).Methods("GET")
|
||||||
api.Router.HandleFunc("/blockchain/header", api.apiBlockchainHeaderFunc).Methods("GET")
|
api.Router.HandleFunc("/blockchain/header", api.apiBlockchainHeaderFunc).Methods("GET")
|
||||||
api.Router.HandleFunc("/blockchain/append", api.apiBlockchainAppend).Methods("POST")
|
api.Router.HandleFunc("/blockchain/append", api.apiBlockchainAppend).Methods("POST")
|
||||||
api.Router.HandleFunc("/blockchain/read", api.apiBlockchainRead).Methods("GET")
|
api.Router.HandleFunc("/blockchain/read", api.apiBlockchainRead).Methods("GET")
|
||||||
api.Router.HandleFunc("/blockchain/file/add", api.apiBlockchainFileAdd).Methods("POST")
|
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/list", api.apiBlockchainFileList).Methods("GET")
|
||||||
api.Router.HandleFunc("/blockchain/file/delete", api.apiBlockchainFileDelete).Methods("POST")
|
api.Router.HandleFunc("/blockchain/file/delete", api.apiBlockchainFileDelete).Methods("POST")
|
||||||
api.Router.HandleFunc("/blockchain/file/update", api.apiBlockchainFileUpdate).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/list", api.apiProfileList).Methods("GET")
|
||||||
api.Router.HandleFunc("/profile/read", api.apiProfileRead).Methods("GET")
|
api.Router.HandleFunc("/profile/read", api.apiProfileRead).Methods("GET")
|
||||||
api.Router.HandleFunc("/profile/write", api.apiProfileWrite).Methods("POST")
|
api.Router.HandleFunc("/profile/write", api.apiProfileWrite).Methods("POST")
|
||||||
api.Router.HandleFunc("/profile/delete", api.apiProfileDelete).Methods("POST")
|
api.Router.HandleFunc("/profile/delete", api.apiProfileDelete).Methods("POST")
|
||||||
api.Router.HandleFunc("/search", api.apiSearch).Methods("POST")
|
api.Router.HandleFunc("/search", api.apiSearch).Methods("POST")
|
||||||
api.Router.HandleFunc("/search/result", api.apiSearchResult).Methods("GET")
|
api.Router.HandleFunc("/search/result", api.apiSearchResult).Methods("GET")
|
||||||
api.Router.HandleFunc("/search/result/ws", api.apiSearchResultStream).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/statistic", api.apiSearchStatistic).Methods("GET")
|
||||||
api.Router.HandleFunc("/search/terminate", api.apiSearchTerminate).Methods("GET")
|
api.Router.HandleFunc("/search/terminate", api.apiSearchTerminate).Methods("GET")
|
||||||
api.Router.HandleFunc("/explore", api.apiExplore).Methods("GET")
|
api.Router.HandleFunc("/explore", api.apiExplore).Methods("GET")
|
||||||
api.Router.HandleFunc("/explore/node", api.apiExploreNodeID).Methods("GET")
|
api.Router.HandleFunc("/explore/node", api.apiExploreNodeID).Methods("GET")
|
||||||
api.Router.HandleFunc("/file/format", api.apiFileFormat).Methods("GET")
|
api.Router.HandleFunc("/file/format", api.apiFileFormat).Methods("GET")
|
||||||
api.Router.HandleFunc("/download/start", api.apiDownloadStart).Methods("GET")
|
api.Router.HandleFunc("/download/start", api.apiDownloadStart).Methods("GET")
|
||||||
api.Router.HandleFunc("/download/status", api.apiDownloadStatus).Methods("GET")
|
api.Router.HandleFunc("/download/status", api.apiDownloadStatus).Methods("GET")
|
||||||
api.Router.HandleFunc("/download/action", api.apiDownloadAction).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", api.apiWarehouseCreateFile).Methods("POST")
|
||||||
api.Router.HandleFunc("/warehouse/create/path", api.apiWarehouseCreateFilePath).Methods("GET")
|
api.Router.HandleFunc("/warehouse/create/path", api.apiWarehouseCreateFilePath).Methods("GET")
|
||||||
api.Router.HandleFunc("/warehouse/read", api.apiWarehouseReadFile).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/read/path", api.apiWarehouseReadFilePath).Methods("GET")
|
||||||
api.Router.HandleFunc("/warehouse/delete", api.apiWarehouseDeleteFile).Methods("GET")
|
api.Router.HandleFunc("/warehouse/delete", api.apiWarehouseDeleteFile).Methods("GET")
|
||||||
api.Router.HandleFunc("/file/read", api.apiFileRead).Methods("GET")
|
api.Router.HandleFunc("/file/read", api.apiFileRead).Methods("GET")
|
||||||
api.Router.HandleFunc("/file/view", api.apiFileView).Methods("GET")
|
api.Router.HandleFunc("/file/view", api.apiFileView).Methods("GET")
|
||||||
|
|
||||||
for _, listen := range ListenAddresses {
|
for _, listen := range ListenAddresses {
|
||||||
go startWebAPI(Backend, listen, UseSSL, CertificateFile, CertificateKey, api.Router, "API", TimeoutRead, TimeoutWrite)
|
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.
|
// 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.
|
// 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) {
|
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{
|
server := &http.Server{
|
||||||
Addr: WebListen,
|
Addr: WebListen,
|
||||||
Handler: Handler,
|
Handler: Handler,
|
||||||
ReadTimeout: ReadTimeout, // ReadTimeout is the maximum duration for reading the entire request, including the body.
|
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.
|
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.
|
//IdleTimeout: IdleTimeout, // IdleTimeout is the maximum amount of time to wait for the next request when keep-alives are enabled.
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
if UseSSL {
|
if UseSSL {
|
||||||
// HTTPS
|
// HTTPS
|
||||||
if err := server.ListenAndServeTLS(CertificateFile, CertificateKey); err != nil {
|
if err := server.ListenAndServeTLS(CertificateFile, CertificateKey); err != nil {
|
||||||
Backend.LogError("startWebAPI", "Error listening on '%s': %v\n", WebListen, err)
|
Backend.LogError("startWebAPI", "Error listening on '%s': %v\n", WebListen, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// HTTP
|
// HTTP
|
||||||
if err := server.ListenAndServe(); err != nil {
|
if err := server.ListenAndServe(); err != nil {
|
||||||
Backend.LogError("startWebAPI", "Error listening on '%s': %v\n", WebListen, err)
|
Backend.LogError("startWebAPI", "Error listening on '%s': %v\n", WebListen, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeJSON encodes the data as JSON
|
// EncodeJSON encodes the data as JSON
|
||||||
func EncodeJSON(Backend *core.Backend, w http.ResponseWriter, r *http.Request, data interface{}) (err error) {
|
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)
|
err = json.NewEncoder(w).Encode(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Backend.LogError("EncodeJSON", "Error writing data for route '%s': %v\n", r.URL.Path, err)
|
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.
|
// 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.
|
// 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) {
|
func DecodeJSON(w http.ResponseWriter, r *http.Request, data interface{}) (err error) {
|
||||||
if r.Body == nil {
|
if r.Body == nil {
|
||||||
http.Error(w, "", http.StatusBadRequest)
|
http.Error(w, "", http.StatusBadRequest)
|
||||||
return errors.New("no data")
|
return errors.New("no data")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.NewDecoder(r.Body).Decode(data)
|
err = json.NewDecoder(r.Body).Decode(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "", http.StatusBadRequest)
|
http.Error(w, "", http.StatusBadRequest)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// authenticateMiddleware returns a middleware function to be used with mux.Router.Use(). It handles all authentication functionality.
|
// 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 {
|
func (api *WebapiInstance) authenticateMiddleware(APIKey uuid.UUID) func(http.Handler) http.Handler {
|
||||||
return (func(next http.Handler) http.Handler {
|
return (func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
keyID, err := uuid.Parse(r.Header.Get("x-api-key"))
|
keyID, err := uuid.Parse(r.Header.Get("x-api-key"))
|
||||||
if err != nil { // special case for some paths
|
if err != nil { // special case for some paths
|
||||||
for _, exceptPath := range api.AllowKeyInParam {
|
for _, exceptPath := range api.AllowKeyInParam {
|
||||||
if exceptPath == r.URL.Path {
|
if exceptPath == r.URL.Path {
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
keyID, err = uuid.Parse(r.Form.Get("k"))
|
keyID, err = uuid.Parse(r.Form.Get("k"))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil { // Invalid key format
|
if err != nil { // Invalid key format
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if keyID != APIKey {
|
if keyID != APIKey {
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package webapi
|
package webapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Test function
|
// Test function
|
||||||
func TestDecodeBlake3Hash(t *testing.T) {
|
func TestDecodeBlake3Hash(t *testing.T) {
|
||||||
hash, bool := DecodeBlake3Hash("")
|
hash, bool := DecodeBlake3Hash("")
|
||||||
fmt.Println(hash)
|
fmt.Println(hash)
|
||||||
fmt.Println(bool)
|
fmt.Println(bool)
|
||||||
}
|
}
|
||||||
|
|||||||
534
webapi/Search.go
534
webapi/Search.go
@@ -16,61 +16,61 @@ Author: Peter Kleissner
|
|||||||
package webapi
|
package webapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"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.
|
// 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 {
|
type SearchRequest struct {
|
||||||
Term string `json:"term"` // Search term.
|
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.
|
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.
|
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".
|
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".
|
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.
|
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.
|
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.
|
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.
|
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.
|
SizeMin int `json:"sizemin"` // Min file size in bytes. -1 = not used.
|
||||||
SizeMax int `json:"sizemax"` // Max file size in bytes. -1 = not used.
|
SizeMax int `json:"sizemax"` // Max file size in bytes. -1 = not used.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort orders
|
// Sort orders
|
||||||
const (
|
const (
|
||||||
SortNone = 0 // No sorting. Results are returned as they come in.
|
SortNone = 0 // No sorting. Results are returned as they come in.
|
||||||
SortRelevanceAsc = 1 // Least relevant results first.
|
SortRelevanceAsc = 1 // Least relevant results first.
|
||||||
SortRelevanceDec = 2 // Most relevant results first.
|
SortRelevanceDec = 2 // Most relevant results first.
|
||||||
SortDateAsc = 3 // Oldest first.
|
SortDateAsc = 3 // Oldest first.
|
||||||
SortDateDesc = 4 // Newest first.
|
SortDateDesc = 4 // Newest first.
|
||||||
SortNameAsc = 5 // File name ascending. The folder name is not used for sorting.
|
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.
|
SortNameDesc = 6 // File name descending. The folder name is not used for sorting.
|
||||||
SortSizeAsc = 7 // File size ascending. Smallest files first.
|
SortSizeAsc = 7 // File size ascending. Smallest files first.
|
||||||
SortSizeDesc = 8 // File size descending. Largest 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.
|
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.
|
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
|
// SearchRequestResponse is the result to the initial search request
|
||||||
type SearchRequestResponse struct {
|
type SearchRequestResponse struct {
|
||||||
ID uuid.UUID `json:"id"` // ID of the search job. This is used to get the results.
|
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
|
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.
|
// SearchResult contains the search results.
|
||||||
type SearchResult struct {
|
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
|
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
|
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.
|
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.
|
// SearchStatistic contains statistics on search results. Statistics are always calculated over all results, regardless of any applied runtime filters.
|
||||||
type SearchStatistic struct {
|
type SearchStatistic struct {
|
||||||
SearchStatisticData
|
SearchStatisticData
|
||||||
Status int `json:"status"` // Status: 0 = Success
|
Status int `json:"status"` // Status: 0 = Success
|
||||||
IsTerminated bool `json:"terminated"` // Whether the search is terminated, meaning that statistics won't change
|
IsTerminated bool `json:"terminated"` // Whether the search is terminated, meaning that statistics won't change
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiDateFormat = "2006-01-02 15:04:05"
|
const apiDateFormat = "2006-01-02 15:04:05"
|
||||||
@@ -84,29 +84,29 @@ Result: 200 on success with JSON SearchRequestResponse
|
|||||||
400 on invalid JSON
|
400 on invalid JSON
|
||||||
*/
|
*/
|
||||||
func (api *WebapiInstance) apiSearch(w http.ResponseWriter, r *http.Request) {
|
func (api *WebapiInstance) apiSearch(w http.ResponseWriter, r *http.Request) {
|
||||||
var input SearchRequest
|
var input SearchRequest
|
||||||
if err := DecodeJSON(w, r, &input); err != nil {
|
if err := DecodeJSON(w, r, &input); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.Timeout <= 0 {
|
if input.Timeout <= 0 {
|
||||||
input.Timeout = 20
|
input.Timeout = 20
|
||||||
}
|
}
|
||||||
if input.MaxResults <= 0 {
|
if input.MaxResults <= 0 {
|
||||||
input.MaxResults = 200
|
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.
|
// 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 {
|
for _, terminate := range input.TerminateID {
|
||||||
if job := api.JobLookup(terminate); job != nil {
|
if job := api.JobLookup(terminate); job != nil {
|
||||||
job.Terminate()
|
job.Terminate()
|
||||||
api.RemoveJob(job)
|
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.
|
Result: 200 with JSON structure SearchResult. Check the field status.
|
||||||
*/
|
*/
|
||||||
func (api *WebapiInstance) apiSearchResult(w http.ResponseWriter, r *http.Request) {
|
func (api *WebapiInstance) apiSearchResult(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
jobID, err := uuid.Parse(r.Form.Get("id"))
|
jobID, err := uuid.Parse(r.Form.Get("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "", http.StatusBadRequest)
|
http.Error(w, "", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
limit, err := strconv.Atoi(r.Form.Get("limit"))
|
limit, err := strconv.Atoi(r.Form.Get("limit"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
limit = 100
|
limit = 100
|
||||||
}
|
}
|
||||||
offset, errOffset := strconv.Atoi(r.Form.Get("offset"))
|
offset, errOffset := strconv.Atoi(r.Form.Get("offset"))
|
||||||
|
|
||||||
// find the job ID
|
// find the job ID
|
||||||
job := api.JobLookup(jobID)
|
job := api.JobLookup(jobID)
|
||||||
if job == nil {
|
if job == nil {
|
||||||
EncodeJSON(api.backend, w, r, SearchResult{Status: 2})
|
EncodeJSON(api.backend, w, r, SearchResult{Status: 2})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// filters and sort parameter
|
// filters and sort parameter
|
||||||
if filterReset, _ := strconv.ParseBool(r.Form.Get("reset")); filterReset {
|
if filterReset, _ := strconv.ParseBool(r.Form.Get("reset")); filterReset {
|
||||||
fileType, _ := strconv.Atoi(r.Form.Get("filetype"))
|
fileType, _ := strconv.Atoi(r.Form.Get("filetype"))
|
||||||
fileFormat, _ := strconv.Atoi(r.Form.Get("fileformat"))
|
fileFormat, _ := strconv.Atoi(r.Form.Get("fileformat"))
|
||||||
dateFrom := r.Form.Get("from")
|
dateFrom := r.Form.Get("from")
|
||||||
dateTo := r.Form.Get("to")
|
dateTo := r.Form.Get("to")
|
||||||
sort, _ := strconv.Atoi(r.Form.Get("sort"))
|
sort, _ := strconv.Atoi(r.Form.Get("sort"))
|
||||||
sizeMin, _ := strconv.Atoi(r.Form.Get("sizemin"))
|
sizeMin, _ := strconv.Atoi(r.Form.Get("sizemin"))
|
||||||
sizeMax, _ := strconv.Atoi(r.Form.Get("sizemax"))
|
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
|
// query all results
|
||||||
var resultFiles []*apiFile
|
var resultFiles []*apiFile
|
||||||
if errOffset == nil {
|
if errOffset == nil {
|
||||||
resultFiles = job.ReturnResult(offset, limit)
|
resultFiles = job.ReturnResult(offset, limit)
|
||||||
} else {
|
} else {
|
||||||
resultFiles = job.ReturnNext(limit)
|
resultFiles = job.ReturnNext(limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
var result SearchResult
|
var result SearchResult
|
||||||
result.Files = []apiFile{}
|
result.Files = []apiFile{}
|
||||||
|
|
||||||
// loop over results
|
// loop over results
|
||||||
for n := range resultFiles {
|
for n := range resultFiles {
|
||||||
result.Files = append(result.Files, *resultFiles[n])
|
result.Files = append(result.Files, *resultFiles[n])
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the status
|
// set the status
|
||||||
if len(result.Files) > 0 {
|
if len(result.Files) > 0 {
|
||||||
if job.IsSearchResults() {
|
if job.IsSearchResults() {
|
||||||
result.Status = 0 // 0 = Success with results
|
result.Status = 0 // 0 = Success with results
|
||||||
} else {
|
} else {
|
||||||
result.Status = 1 // No more results to expect
|
result.Status = 1 // No more results to expect
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch job.Status {
|
switch job.Status {
|
||||||
case SearchStatusLive:
|
case SearchStatusLive:
|
||||||
result.Status = 3 // No results yet available keep trying
|
result.Status = 3 // No results yet available keep trying
|
||||||
|
|
||||||
case SearchStatusTerminated:
|
case SearchStatusTerminated:
|
||||||
result.Status = 1 // No more results to expect
|
result.Status = 1 // No more results to expect
|
||||||
|
|
||||||
default: // SearchStatusNoIndex, SearchStatusNotStarted
|
default: // SearchStatusNoIndex, SearchStatusNotStarted
|
||||||
result.Status = 1 // No more results to expect
|
result.Status = 1 // No more results to expect
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// embedded statistics?
|
// embedded statistics?
|
||||||
if returnStats, _ := strconv.ParseBool(r.Form.Get("stats")); returnStats {
|
if returnStats, _ := strconv.ParseBool(r.Form.Get("stats")); returnStats {
|
||||||
result.Statistic = job.Statistics()
|
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.
|
Limit is optional. Not used if ommitted or 0.
|
||||||
*/
|
*/
|
||||||
func (api *WebapiInstance) apiSearchResultStream(w http.ResponseWriter, r *http.Request) {
|
func (api *WebapiInstance) apiSearchResultStream(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
jobID, err := uuid.Parse(r.Form.Get("id"))
|
jobID, err := uuid.Parse(r.Form.Get("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "", http.StatusBadRequest)
|
http.Error(w, "", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
limit, err := strconv.Atoi(r.Form.Get("limit"))
|
limit, err := strconv.Atoi(r.Form.Get("limit"))
|
||||||
useLimit := err == nil
|
useLimit := err == nil
|
||||||
|
|
||||||
// look up the job
|
// look up the job
|
||||||
job := api.JobLookup(jobID)
|
job := api.JobLookup(jobID)
|
||||||
if job == nil {
|
if job == nil {
|
||||||
EncodeJSON(api.backend, w, r, SearchResult{Status: 2})
|
EncodeJSON(api.backend, w, r, SearchResult{Status: 2})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// upgrade to websocket
|
// upgrade to websocket
|
||||||
conn, err := WSUpgrader.Upgrade(w, r, nil)
|
conn, err := WSUpgrader.Upgrade(w, r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// gorilla will automatically respond with "400 Bad Request", no other response is therefore necessary
|
// gorilla will automatically respond with "400 Bad Request", no other response is therefore necessary
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
// loop to get new results and send out via the web socket.
|
// 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.
|
// Only exit if limit is reached if used, otherwise only if there are no result or the connection breaks.
|
||||||
for {
|
for {
|
||||||
// query all results
|
// query all results
|
||||||
var resultFiles []*apiFile
|
var resultFiles []*apiFile
|
||||||
|
|
||||||
queryCount := 1
|
queryCount := 1
|
||||||
if useLimit {
|
if useLimit {
|
||||||
queryCount = limit
|
queryCount = limit
|
||||||
}
|
}
|
||||||
resultFiles = job.ReturnNext(queryCount)
|
resultFiles = job.ReturnNext(queryCount)
|
||||||
|
|
||||||
if useLimit {
|
if useLimit {
|
||||||
limit -= len(resultFiles)
|
limit -= len(resultFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
// loop over results
|
// loop over results
|
||||||
var result SearchResult
|
var result SearchResult
|
||||||
result.Files = []apiFile{}
|
result.Files = []apiFile{}
|
||||||
|
|
||||||
for n := range resultFiles {
|
for n := range resultFiles {
|
||||||
result.Files = append(result.Files, *resultFiles[n])
|
result.Files = append(result.Files, *resultFiles[n])
|
||||||
}
|
}
|
||||||
|
|
||||||
if !job.IsSearchResults() {
|
if !job.IsSearchResults() {
|
||||||
result.Status = 1 // No more results to expect
|
result.Status = 1 // No more results to expect
|
||||||
|
|
||||||
if len(result.Files) == 0 {
|
if len(result.Files) == 0 {
|
||||||
conn.WriteJSON(result) // final message
|
conn.WriteJSON(result) // final message
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if no results, stall
|
// if no results, stall
|
||||||
if len(result.Files) == 0 {
|
if len(result.Files) == 0 {
|
||||||
time.Sleep(time.Millisecond * 100)
|
time.Sleep(time.Millisecond * 100)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// send out the results via the websocket
|
// send out the results via the websocket
|
||||||
if err := conn.WriteJSON(result); err != nil {
|
if err := conn.WriteJSON(result); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether to continue. If the limit is used break once all done.
|
// Check whether to continue. If the limit is used break once all done.
|
||||||
if (useLimit && limit <= 0) || result.Status == 1 {
|
if (useLimit && limit <= 0) || result.Status == 1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -302,25 +302,25 @@ Response: 204 Empty
|
|||||||
*/
|
*/
|
||||||
func (api *WebapiInstance) apiSearchTerminate(w http.ResponseWriter, r *http.Request) {
|
func (api *WebapiInstance) apiSearchTerminate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
jobID, err := uuid.Parse(r.Form.Get("id"))
|
jobID, err := uuid.Parse(r.Form.Get("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "", http.StatusBadRequest)
|
http.Error(w, "", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// look up the job
|
// look up the job
|
||||||
job := api.JobLookup(jobID)
|
job := api.JobLookup(jobID)
|
||||||
if job == nil {
|
if job == nil {
|
||||||
http.Error(w, "", http.StatusNotFound)
|
http.Error(w, "", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// terminate and remove it from the list
|
// terminate and remove it from the list
|
||||||
job.Terminate()
|
job.Terminate()
|
||||||
api.RemoveJob(job)
|
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).
|
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) {
|
func (api *WebapiInstance) apiSearchStatistic(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
jobID, err := uuid.Parse(r.Form.Get("id"))
|
jobID, err := uuid.Parse(r.Form.Get("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "", http.StatusBadRequest)
|
http.Error(w, "", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// find the job ID
|
// find the job ID
|
||||||
job := api.JobLookup(jobID)
|
job := api.JobLookup(jobID)
|
||||||
if job == nil {
|
if job == nil {
|
||||||
EncodeJSON(api.backend, w, r, SearchStatistic{Status: 2})
|
EncodeJSON(api.backend, w, r, SearchStatistic{Status: 2})
|
||||||
return
|
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.
|
Result: 200 with JSON structure SearchResult. Check the field status.
|
||||||
*/
|
*/
|
||||||
func (api *WebapiInstance) apiExplore(w http.ResponseWriter, r *http.Request) {
|
func (api *WebapiInstance) apiExplore(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
offset, _ := strconv.Atoi(r.Form.Get("offset"))
|
offset, _ := strconv.Atoi(r.Form.Get("offset"))
|
||||||
limit, err := strconv.Atoi(r.Form.Get("limit"))
|
limit, err := strconv.Atoi(r.Form.Get("limit"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
limit = 100
|
limit = 100
|
||||||
}
|
}
|
||||||
|
|
||||||
fileType, err := strconv.Atoi(r.Form.Get("type"))
|
fileType, err := strconv.Atoi(r.Form.Get("type"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fileType = -1
|
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.
|
Result: 200 with JSON structure SearchResult. Check the field status.
|
||||||
*/
|
*/
|
||||||
func (api *WebapiInstance) apiExploreNodeID(w http.ResponseWriter, r *http.Request) {
|
func (api *WebapiInstance) apiExploreNodeID(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
offset, _ := strconv.Atoi(r.Form.Get("offset"))
|
offset, _ := strconv.Atoi(r.Form.Get("offset"))
|
||||||
limit, err := strconv.Atoi(r.Form.Get("limit"))
|
limit, err := strconv.Atoi(r.Form.Get("limit"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
limit = 100
|
limit = 100
|
||||||
}
|
}
|
||||||
// ID fields for results for a specific node ID.
|
// ID fields for results for a specific node ID.
|
||||||
NodeId, _ := DecodeBlake3Hash(r.Form.Get("NodeID"))
|
NodeId, _ := DecodeBlake3Hash(r.Form.Get("NodeID"))
|
||||||
|
|
||||||
fileType, err := strconv.Atoi(r.Form.Get("type"))
|
fileType, err := strconv.Atoi(r.Form.Get("type"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fileType = -1
|
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
|
// 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{
|
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)
|
resultFiles := api.queryRecentShared(api.backend, fileType, uint64(limit*20/100), uint64(offset), uint64(limit), nodeID, nodeIDState)
|
||||||
|
|
||||||
var result SearchResult
|
var result SearchResult
|
||||||
result.Files = []apiFile{}
|
result.Files = []apiFile{}
|
||||||
|
|
||||||
// loop over results
|
// loop over results
|
||||||
for n := range resultFiles {
|
for n := range resultFiles {
|
||||||
result.Files = append(result.Files, blockRecordFileToAPI(resultFiles[n]))
|
result.Files = append(result.Files, blockRecordFileToAPI(resultFiles[n]))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(result.Files) == 0 {
|
if len(result.Files) == 0 {
|
||||||
result.Status = 3 // No results yet available keep trying
|
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) {
|
func (input *SearchRequest) Parse() (Timeout time.Duration) {
|
||||||
if input.Timeout == 0 {
|
if input.Timeout == 0 {
|
||||||
Timeout = time.Second * 20 // default timeout: 20 seconds
|
Timeout = time.Second * 20 // default timeout: 20 seconds
|
||||||
} else {
|
} else {
|
||||||
Timeout = time.Duration(input.Timeout) * time.Second
|
Timeout = time.Duration(input.Timeout) * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToSearchFilter converts the user input to a valid search filter
|
// ToSearchFilter converts the user input to a valid search filter
|
||||||
func (input *SearchRequest) ToSearchFilter() (output SearchFilter) {
|
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) {
|
func inputToSearchFilter(Sort, FileType, FileFormat int, DateFrom, DateTo string, SizeMin, SizeMax int) (output SearchFilter) {
|
||||||
output.Sort = Sort
|
output.Sort = Sort
|
||||||
output.FileType = FileType
|
output.FileType = FileType
|
||||||
output.FileFormat = FileFormat
|
output.FileFormat = FileFormat
|
||||||
output.SizeMin = SizeMin
|
output.SizeMin = SizeMin
|
||||||
output.SizeMax = SizeMax
|
output.SizeMax = SizeMax
|
||||||
|
|
||||||
dateFrom, errFrom := time.Parse(apiDateFormat, DateFrom)
|
dateFrom, errFrom := time.Parse(apiDateFormat, DateFrom)
|
||||||
dateTo, errTo := time.Parse(apiDateFormat, DateTo)
|
dateTo, errTo := time.Parse(apiDateFormat, DateTo)
|
||||||
if errFrom == nil && errTo == nil && dateFrom.Before(dateTo) {
|
if errFrom == nil && errTo == nil && dateFrom.Before(dateTo) {
|
||||||
output.DateFrom = dateFrom
|
output.DateFrom = dateFrom
|
||||||
output.DateTo = dateTo
|
output.DateTo = dateTo
|
||||||
output.IsDates = true
|
output.IsDates = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,98 +6,98 @@ Author: Peter Kleissner
|
|||||||
package webapi
|
package webapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/PeernetOfficial/core"
|
"github.com/PeernetOfficial/core"
|
||||||
"github.com/PeernetOfficial/core/blockchain"
|
"github.com/PeernetOfficial/core/blockchain"
|
||||||
)
|
)
|
||||||
|
|
||||||
// queryRecentShared returns recently shared files on the network from random peers until the limit is reached.
|
// 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) {
|
func (api *WebapiInstance) queryRecentShared(backend *core.Backend, fileType int, limitPeer, offsetTotal, limitTotal uint64, nodeID []byte, nodeIDState bool) (files []blockchain.BlockRecordFile) {
|
||||||
if limitPeer == 0 {
|
if limitPeer == 0 {
|
||||||
limitPeer = 1
|
limitPeer = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign peer list as an empty array
|
// Assign peer list as an empty array
|
||||||
var peerList []*core.PeerInfo
|
var peerList []*core.PeerInfo
|
||||||
|
|
||||||
// check if the NodeID is provided or not
|
// check if the NodeID is provided or not
|
||||||
if len(nodeID) == 0 && !nodeIDState {
|
if len(nodeID) == 0 && !nodeIDState {
|
||||||
// Use the peer list to know about active peers. Random order!
|
// Use the peer list to know about active peers. Random order!
|
||||||
peerList = api.backend.PeerlistGet()
|
peerList = api.backend.PeerlistGet()
|
||||||
} else {
|
} else {
|
||||||
// Get peer information based on the NodeID provided
|
// Get peer information based on the NodeID provided
|
||||||
_, peer, err := api.backend.FindNode(nodeID, time.Second*5)
|
_, peer, err := api.backend.FindNode(nodeID, time.Second*5)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
peerList = append(peerList, peer)
|
peerList = append(peerList, peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Files from peers exceeding the limit. It is used if from all peers the total limit is not reached.
|
// Files from peers exceeding the limit. It is used if from all peers the total limit is not reached.
|
||||||
var filesSeconday []blockchain.BlockRecordFile
|
var filesSeconday []blockchain.BlockRecordFile
|
||||||
|
|
||||||
for _, peer := range peerList {
|
for _, peer := range peerList {
|
||||||
if peer.BlockchainHeight == 0 {
|
if peer.BlockchainHeight == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var filesFromPeer uint64
|
var filesFromPeer uint64
|
||||||
|
|
||||||
// decode blocks from top down
|
// decode blocks from top down
|
||||||
blockLoop:
|
blockLoop:
|
||||||
for blockN := peer.BlockchainHeight - 1; blockN > 0; blockN-- {
|
for blockN := peer.BlockchainHeight - 1; blockN > 0; blockN-- {
|
||||||
blockDecoded, _, found, _ := backend.ReadBlock(peer.PublicKey, peer.BlockchainVersion, blockN)
|
blockDecoded, _, found, _ := backend.ReadBlock(peer.PublicKey, peer.BlockchainVersion, blockN)
|
||||||
if !found {
|
if !found {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, record := range blockDecoded.RecordsDecoded {
|
for _, record := range blockDecoded.RecordsDecoded {
|
||||||
if file, ok := record.(blockchain.BlockRecordFile); ok && isFileTypeMatchBlock(&file, fileType) {
|
if file, ok := record.(blockchain.BlockRecordFile); ok && isFileTypeMatchBlock(&file, fileType) {
|
||||||
// add the tags 'Shared By Count' and 'Shared By GeoIP'
|
// add the tags 'Shared By Count' and 'Shared By GeoIP'
|
||||||
file.Tags = append(file.Tags, blockchain.TagFromNumber(blockchain.TagSharedByCount, 1))
|
file.Tags = append(file.Tags, blockchain.TagFromNumber(blockchain.TagSharedByCount, 1))
|
||||||
if latitude, longitude, valid := api.Peer2GeoIP(peer); valid {
|
if latitude, longitude, valid := api.Peer2GeoIP(peer); valid {
|
||||||
sharedByGeoIP := fmt.Sprintf("%.4f", latitude) + "," + fmt.Sprintf("%.4f", longitude)
|
sharedByGeoIP := fmt.Sprintf("%.4f", latitude) + "," + fmt.Sprintf("%.4f", longitude)
|
||||||
file.Tags = append(file.Tags, blockchain.TagFromText(blockchain.TagSharedByGeoIP, sharedByGeoIP))
|
file.Tags = append(file.Tags, blockchain.TagFromText(blockchain.TagSharedByGeoIP, sharedByGeoIP))
|
||||||
}
|
}
|
||||||
|
|
||||||
// found a new file! append.
|
// found a new file! append.
|
||||||
if filesFromPeer < limitPeer {
|
if filesFromPeer < limitPeer {
|
||||||
filesFromPeer++
|
filesFromPeer++
|
||||||
|
|
||||||
if offsetTotal > 0 {
|
if offsetTotal > 0 {
|
||||||
offsetTotal--
|
offsetTotal--
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
files = append(files, file)
|
files = append(files, file)
|
||||||
|
|
||||||
if uint64(len(files)) >= limitTotal {
|
if uint64(len(files)) >= limitTotal {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if uint64(len(filesSeconday)) < limitTotal-uint64(len(files)) {
|
} else if uint64(len(filesSeconday)) < limitTotal-uint64(len(files)) {
|
||||||
filesSeconday = append(filesSeconday, file)
|
filesSeconday = append(filesSeconday, file)
|
||||||
} else {
|
} else {
|
||||||
break blockLoop
|
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.
|
// 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 {
|
func isFileTypeMatchBlock(file *blockchain.BlockRecordFile, fileType int) bool {
|
||||||
if fileType == -1 {
|
if fileType == -1 {
|
||||||
return true
|
return true
|
||||||
} else if fileType == -2 {
|
} else if fileType == -2 {
|
||||||
return file.Type == core.TypeBinary || file.Type == core.TypeCompressed || file.Type == core.TypeContainer || file.Type == core.TypeExecutable
|
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