diff --git a/files.go b/files.go index 62801f7..c67ef1d 100644 --- a/files.go +++ b/files.go @@ -14,6 +14,7 @@ import ( "github.com/PeernetOfficial/core/protocol" "github.com/PeernetOfficial/core/warehouse" "github.com/google/uuid" + "math" "path/filepath" "time" ) @@ -230,3 +231,72 @@ func SearchResult(api *webapi.WebapiInstance, jobID uuid.UUID) (*webapi.SearchRe return nil, errors.New("search not successful") } + +// Download and abstracted function that starts downloading a file +// and returns the ID which can be used to track the files +// download status +func Download(api *webapi.WebapiInstance, hashStr string, nodeIDStr string, path string) (*uuid.UUID, error) { + // validate hashes, must be blake3 + hash, valid1 := webapi.DecodeBlake3Hash(hashStr) + nodeID, valid2 := webapi.DecodeBlake3Hash(nodeIDStr) + if !valid1 || !valid2 { + //http.Error(w, "", http.StatusBadRequest) + return nil, errors.New("hash or node ID was not valid") + } + + filePath := path + if filePath == "" { + // http.Error(w, "", http.StatusBadRequest) + return nil, errors.New("file path not provided") + } + + ID := uuid.New() + + info := &webapi.DownloadInfo{Backend: api.Backend, Api: api, ID: ID, Created: time.Now(), Hash: hash, NodeID: nodeID} + + // create the file immediately + err := info.InitDiskFile(filePath) + if err != nil { + return nil, err + } + + // add the download to the list + api.DownloadAdd(info) + + // start the download! + go info.Start() + + return &ID, nil +} + +// DownloadStatus Abstracted function that finds the status of a downloaded files +// based on the download ID provided and returns with the appropriate information +func DownloadStatus(api *webapi.WebapiInstance, DownloadID *uuid.UUID) (*webapi.ApiResponseDownloadStatus, error) { + + info := api.DownloadLookup(*DownloadID) + if info == nil { + //EncodeJSON(api.Backend, w, r, apiResponseDownloadStatus{APIStatus: DownloadResponseIDNotFound}) + return nil, errors.New("download ID not found") + } + + info.RLock() + + response := webapi.ApiResponseDownloadStatus{APIStatus: webapi.DownloadResponseSuccess, ID: info.ID, DownloadStatus: info.Status} + + if info.Status >= webapi.DownloadWaitSwarm { + response.File = info.File + + response.Progress.TotalSize = info.File.Size + response.Progress.DownloadedSize = info.DiskFile.StoredSize + + response.Progress.Percentage = math.Round(float64(info.DiskFile.StoredSize)/float64(info.File.Size)*100*100) / 100 + } + + if info.Status >= webapi.DownloadActive { + response.Swarm.CountPeers = info.Swarm.CountPeers + } + + info.RUnlock() + + return &response, nil +} diff --git a/webapi/API.go b/webapi/API.go index 1f435ec..28f8fad 100644 --- a/webapi/API.go +++ b/webapi/API.go @@ -34,7 +34,7 @@ type WebapiInstance struct { allJobsMutex sync.RWMutex // download info - downloads map[uuid.UUID]*downloadInfo + downloads map[uuid.UUID]*DownloadInfo downloadsMutex sync.RWMutex } @@ -49,7 +49,7 @@ var WSUpgrader = websocket.Upgrader{ } // 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. 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 { @@ -59,9 +59,9 @@ func Start(Backend *core.Backend, ListenAddresses []string, UseSSL bool, Certifi api = &WebapiInstance{ Backend: Backend, Router: mux.NewRouter(), - AllowKeyInParam: []string{"/file/read", "/file/view"}, + AllowKeyInParam: []string{"/File/read", "/File/view"}, allJobs: make(map[uuid.UUID]*SearchJob), - downloads: make(map[uuid.UUID]*downloadInfo), + downloads: make(map[uuid.UUID]*DownloadInfo), } if APIKey != uuid.Nil { @@ -69,17 +69,17 @@ func Start(Backend *core.Backend, ListenAddresses []string, UseSSL bool, Certifi } 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("/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("/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") @@ -90,17 +90,17 @@ func Start(Backend *core.Backend, ListenAddresses []string, UseSSL bool, Certifi 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("/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/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("/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("/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) @@ -109,8 +109,8 @@ func Start(Backend *core.Backend, ListenAddresses []string, UseSSL bool, Certifi 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. +// 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) diff --git a/webapi/Blockchain.go b/webapi/Blockchain.go index 119631f..a33f94a 100644 --- a/webapi/Blockchain.go +++ b/webapi/Blockchain.go @@ -43,7 +43,7 @@ type apiBlockchainBlockRaw struct { } type apiBlockchainBlockStatus struct { - Status int `json:"status"` // See blockchain.StatusX. + Status int `json:"Status"` // See blockchain.StatusX. Height uint64 `json:"height"` // Height of the blockchain (number of blocks). Version uint64 `json:"version"` // Version of the blockchain. } @@ -73,7 +73,7 @@ func (api *WebapiInstance) apiBlockchainAppend(w http.ResponseWriter, r *http.Re } type apiBlockchainBlock struct { - Status int `json:"status"` // See blockchain.StatusX. + Status int `json:"Status"` // See blockchain.StatusX. PeerID string `json:"peerid"` // Peer ID hex encoded. LastBlockHash []byte `json:"lastblockhash"` // Hash of the last block. Blake3. BlockchainVersion uint64 `json:"blockchainversion"` // Blockchain version diff --git a/webapi/Download Transfer.go b/webapi/Download Transfer.go index b3a59d2..7c284fa 100644 --- a/webapi/Download Transfer.go +++ b/webapi/Download Transfer.go @@ -1,202 +1,202 @@ -/* -File Name: Download Transfer.go -Copyright: 2021 Peernet Foundation s.r.o. -Author: Peter Kleissner - -Temporary download code to provide dummy results for testing. To be replaced! -*/ - -package webapi - -import ( - "bytes" - "os" - "time" - - "github.com/PeernetOfficial/core/warehouse" -) - -// Starts the download. -func (info *downloadInfo) Start() { - // current user? - if bytes.Equal(info.nodeID, info.backend.SelfNodeID()) { - info.DownloadSelf() - return - } - - for n := 0; n < 3 && info.peer == nil; n++ { - _, info.peer, _ = info.backend.FindNode(info.nodeID, time.Second*5) - - if info.status == DownloadCanceled { - return - } - } - - if info.peer != nil { - info.Download() - } else { - info.status = DownloadCanceled - } -} - -func (info *downloadInfo) Download() { - //fmt.Printf("Download start of %s\n", hex.EncodeToString(info.hash)) - - // try to download the entire file - reader, fileSize, transferSize, err := FileStartReader(info.peer, info.hash, 0, 0, nil) - if reader != nil { - defer reader.Close() - } - if err != nil { - info.status = DownloadCanceled - return - } else if fileSize != transferSize { - info.status = DownloadCanceled - return - } - - info.file.Size = fileSize - info.status = DownloadActive - - // download in a loop - var fileOffset, totalRead uint64 - dataRemaining := fileSize - readSize := uint64(4096) - - for dataRemaining > 0 { - //fmt.Printf("data remaining: downloaded %d from total %d = %d %%\n", totalRead, fileSize, totalRead*100/fileSize) - if dataRemaining < readSize { - readSize = dataRemaining - } - - data := make([]byte, readSize) - n, err := reader.Read(data) - - totalRead += uint64(n) - dataRemaining -= uint64(n) - data = data[:n] - - if err != nil { - info.status = DownloadCanceled - return - } - - info.storeDownloadData(data[:n], fileOffset) - - fileOffset += uint64(n) - } - - //fmt.Printf("data finished: downloaded %d from total %d = %d %%\n", totalRead, fileSize, totalRead*100/fileSize) - - info.Finish() - info.DeleteDefer(time.Hour * 1) // cache the details for 1 hour before removing -} - -// Pause pauses the download. Status is DownloadResponseX. -func (info *downloadInfo) Pause() (status int) { - info.Lock() - defer info.Unlock() - - if info.status != DownloadActive { // The download must be active to be paused. - return DownloadResponseActionInvalid - } - - info.status = DownloadPause - - return DownloadResponseSuccess -} - -// Resume resumes the download. Status is DownloadResponseX. -func (info *downloadInfo) Resume() (status int) { - info.Lock() - defer info.Unlock() - - if info.status != DownloadPause { // The download must be paused to resume. - return DownloadResponseActionInvalid - } - - info.status = DownloadActive - - return DownloadResponseSuccess -} - -// Cancel cancels the download. Status is DownloadResponseX. -func (info *downloadInfo) Cancel() (status int) { - info.Lock() - defer info.Unlock() - - if info.status >= DownloadCanceled { // The download must not be already canceled or finished. - return DownloadResponseActionInvalid - } - - info.status = DownloadCanceled - info.DiskFile.Handle.Close() - - return DownloadResponseSuccess -} - -// Finish marks the download as finished. -func (info *downloadInfo) Finish() (status int) { - info.Lock() - defer info.Unlock() - - if info.status != DownloadActive { // The download must be active. - return DownloadResponseActionInvalid - } - - info.status = DownloadFinished - info.DiskFile.Handle.Close() - - return DownloadResponseSuccess -} - -// initDiskFile creates the target file -func (info *downloadInfo) initDiskFile(path string) (err error) { - info.DiskFile.Name = path - info.DiskFile.Handle, err = os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0666) // 666 : All uses can read/write - - return err -} - -// storeDownloadData stores downloaded data. It does not change the download status. -func (info *downloadInfo) storeDownloadData(data []byte, offset uint64) (status int) { - info.Lock() - defer info.Unlock() - - if info.status != DownloadActive { // The download must be active. - return DownloadResponseActionInvalid - } - - if _, err := info.DiskFile.Handle.WriteAt(data, int64(offset)); err != nil { - return DownloadResponseFileWrite - } - - info.DiskFile.StoredSize += uint64(len(data)) - - return DownloadResponseSuccess -} - -func (info *downloadInfo) DownloadSelf() { - // Check if the file is available in the local warehouse. - _, fileSize, status, _ := info.backend.UserWarehouse.FileExists(info.hash) - if status != warehouse.StatusOK { - info.status = DownloadCanceled - return - } - - info.file.Size = fileSize - info.status = DownloadActive - - // read the file - status, bytesRead, _ := info.backend.UserWarehouse.ReadFile(info.hash, 0, int64(info.file.Size), info.DiskFile.Handle) - - info.DiskFile.StoredSize = uint64(bytesRead) - - if status != warehouse.StatusOK { - info.status = DownloadCanceled - return - } - - info.Finish() - info.DeleteDefer(time.Hour * 1) // cache the details for 1 hour before removing} -} +/* +File Name: Download Transfer.go +Copyright: 2021 Peernet Foundation s.r.o. +Author: Peter Kleissner + +Temporary download code to provide dummy results for testing. To be replaced! +*/ + +package webapi + +import ( + "bytes" + "os" + "time" + + "github.com/PeernetOfficial/core/warehouse" +) + +// Starts the download. +func (info *DownloadInfo) Start() { + // current user? + if bytes.Equal(info.NodeID, info.Backend.SelfNodeID()) { + info.DownloadSelf() + return + } + + for n := 0; n < 3 && info.Peer == nil; n++ { + _, info.Peer, _ = info.Backend.FindNode(info.NodeID, time.Second*5) + + if info.Status == DownloadCanceled { + return + } + } + + if info.Peer != nil { + info.Download() + } else { + info.Status = DownloadCanceled + } +} + +func (info *DownloadInfo) Download() { + //fmt.Printf("Download start of %s\n", hex.EncodeToString(info.Hash)) + + // try to download the entire File + reader, fileSize, transferSize, err := FileStartReader(info.Peer, info.Hash, 0, 0, nil) + if reader != nil { + defer reader.Close() + } + if err != nil { + info.Status = DownloadCanceled + return + } else if fileSize != transferSize { + info.Status = DownloadCanceled + return + } + + info.File.Size = fileSize + info.Status = DownloadActive + + // download in a loop + var fileOffset, totalRead uint64 + dataRemaining := fileSize + readSize := uint64(4096) + + for dataRemaining > 0 { + //fmt.Printf("data remaining: downloaded %d from total %d = %d %%\n", totalRead, fileSize, totalRead*100/fileSize) + if dataRemaining < readSize { + readSize = dataRemaining + } + + data := make([]byte, readSize) + n, err := reader.Read(data) + + totalRead += uint64(n) + dataRemaining -= uint64(n) + data = data[:n] + + if err != nil { + info.Status = DownloadCanceled + return + } + + info.storeDownloadData(data[:n], fileOffset) + + fileOffset += uint64(n) + } + + //fmt.Printf("data finished: downloaded %d from total %d = %d %%\n", totalRead, fileSize, totalRead*100/fileSize) + + info.Finish() + info.DeleteDefer(time.Hour * 1) // cache the details for 1 hour before removing +} + +// Pause pauses the download. Status is DownloadResponseX. +func (info *DownloadInfo) Pause() (status int) { + info.Lock() + defer info.Unlock() + + if info.Status != DownloadActive { // The download must be active to be paused. + return DownloadResponseActionInvalid + } + + info.Status = DownloadPause + + return DownloadResponseSuccess +} + +// Resume resumes the download. Status is DownloadResponseX. +func (info *DownloadInfo) Resume() (status int) { + info.Lock() + defer info.Unlock() + + if info.Status != DownloadPause { // The download must be paused to resume. + return DownloadResponseActionInvalid + } + + info.Status = DownloadActive + + return DownloadResponseSuccess +} + +// Cancel cancels the download. Status is DownloadResponseX. +func (info *DownloadInfo) Cancel() (status int) { + info.Lock() + defer info.Unlock() + + if info.Status >= DownloadCanceled { // The download must not be already canceled or finished. + return DownloadResponseActionInvalid + } + + info.Status = DownloadCanceled + info.DiskFile.Handle.Close() + + return DownloadResponseSuccess +} + +// Finish marks the download as finished. +func (info *DownloadInfo) Finish() (status int) { + info.Lock() + defer info.Unlock() + + if info.Status != DownloadActive { // The download must be active. + return DownloadResponseActionInvalid + } + + info.Status = DownloadFinished + info.DiskFile.Handle.Close() + + return DownloadResponseSuccess +} + +// InitDiskFile creates the target File +func (info *DownloadInfo) InitDiskFile(path string) (err error) { + info.DiskFile.Name = path + info.DiskFile.Handle, err = os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0666) // 666 : All uses can read/write + + return err +} + +// storeDownloadData stores downloaded data. It does not change the download Status. +func (info *DownloadInfo) storeDownloadData(data []byte, offset uint64) (status int) { + info.Lock() + defer info.Unlock() + + if info.Status != DownloadActive { // The download must be active. + return DownloadResponseActionInvalid + } + + if _, err := info.DiskFile.Handle.WriteAt(data, int64(offset)); err != nil { + return DownloadResponseFileWrite + } + + info.DiskFile.StoredSize += uint64(len(data)) + + return DownloadResponseSuccess +} + +func (info *DownloadInfo) DownloadSelf() { + // Check if the File is available in the local warehouse. + _, fileSize, status, _ := info.Backend.UserWarehouse.FileExists(info.Hash) + if status != warehouse.StatusOK { + info.Status = DownloadCanceled + return + } + + info.File.Size = fileSize + info.Status = DownloadActive + + // read the File + status, bytesRead, _ := info.Backend.UserWarehouse.ReadFile(info.Hash, 0, int64(info.File.Size), info.DiskFile.Handle) + + info.DiskFile.StoredSize = uint64(bytesRead) + + if status != warehouse.StatusOK { + info.Status = DownloadCanceled + return + } + + info.Finish() + info.DeleteDefer(time.Hour * 1) // cache the details for 1 hour before removing} +} diff --git a/webapi/Download.go b/webapi/Download.go index 6316d65..ee94a42 100644 --- a/webapi/Download.go +++ b/webapi/Download.go @@ -19,51 +19,51 @@ import ( "github.com/google/uuid" ) -type apiResponseDownloadStatus struct { +type ApiResponseDownloadStatus struct { APIStatus int `json:"apistatus"` // Status of the API call. See DownloadResponseX. - ID uuid.UUID `json:"ID"` // Download ID. This can be used to query the latest status and take actions. + ID uuid.UUID `json:"ID"` // Download ID. This can be used to query the latest Status and take actions. DownloadStatus int `json:"downloadstatus"` // Status of the download. See DownloadX. - File ApiFile `json:"file"` // File information. Only available for status >= DownloadWaitSwarm. + File ApiFile `json:"File"` // File information. Only available for Status >= DownloadWaitSwarm. Progress struct { TotalSize uint64 `json:"totalsize"` // Total size in bytes. DownloadedSize uint64 `json:"downloadedsize"` // Count of bytes download so far. Percentage float64 `json:"percentage"` // Percentage downloaded. Rounded to 2 decimal points. Between 0.00 and 100.00. - } `json:"progress"` // Progress of the download. Only valid for status >= DownloadWaitSwarm. + } `json:"progress"` // Progress of the download. Only valid for Status >= DownloadWaitSwarm. Swarm struct { CountPeers uint64 `json:"countpeers"` // Count of peers participating in the swarm. - } `json:"swarm"` // Information about the swarm. Only valid for status >= DownloadActive. + } `json:"swarm"` // Information about the swarm. Only valid for Status >= DownloadActive. } const ( DownloadResponseSuccess = 0 // Success DownloadResponseIDNotFound = 1 // Error: Download ID not found. - DownloadResponseFileInvalid = 2 // Error: Target file cannot be used. For example, permissions denied to create it. + DownloadResponseFileInvalid = 2 // Error: Target File cannot be used. For example, permissions denied to create it. DownloadResponseActionInvalid = 4 // Error: Invalid action. Pausing a non-active download, resuming a non-paused download, or canceling already canceled or finished download. - DownloadResponseFileWrite = 5 // Error writing file. + DownloadResponseFileWrite = 5 // Error writing File. ) -// Download status list +// Download Status list const ( - DownloadWaitMetadata = 0 // Wait for file metadata. + DownloadWaitMetadata = 0 // Wait for File metadata. DownloadWaitSwarm = 1 // Wait to join swarm. DownloadActive = 2 // Active downloading. It could still be stuck at any percentage (including 0%) if no seeders are available. DownloadPause = 3 // Paused by the user. - DownloadCanceled = 4 // Canceled by the user before the download finished. Once canceled, a new download has to be started if the file shall be downloaded. + DownloadCanceled = 4 // Canceled by the user before the download finished. Once canceled, a new download has to be started if the File shall be downloaded. DownloadFinished = 5 // Download finished 100%. ) /* -apiDownloadStart starts the download of a file. The path is the full path on disk to store the file. -The hash parameter identifies the file to download. The node ID identifies the blockchain (i.e., the "owner" of the file). +apiDownloadStart starts the download of a File. The path is the full path on disk to store the File. +The Hash parameter identifies the File to download. The node ID identifies the blockchain (i.e., the "owner" of the File). -Request: GET /download/start?path=[target path on disk]&hash=[file hash to download]&node=[node ID] -Result: 200 with JSON structure apiResponseDownloadStatus +Request: GET /download/start?path=[target path on disk]&Hash=[File Hash to download]&node=[node ID] +Result: 200 with JSON structure ApiResponseDownloadStatus */ func (api *WebapiInstance) apiDownloadStart(w http.ResponseWriter, r *http.Request) { r.ParseForm() // validate hashes, must be blake3 - hash, valid1 := DecodeBlake3Hash(r.Form.Get("hash")) + hash, valid1 := DecodeBlake3Hash(r.Form.Get("Hash")) nodeID, valid2 := DecodeBlake3Hash(r.Form.Get("node")) if !valid1 || !valid2 { http.Error(w, "", http.StatusBadRequest) @@ -76,28 +76,28 @@ func (api *WebapiInstance) apiDownloadStart(w http.ResponseWriter, r *http.Reque return } - info := &downloadInfo{backend: api.Backend, api: api, id: uuid.New(), created: time.Now(), hash: hash, nodeID: nodeID} + info := &DownloadInfo{Backend: api.Backend, Api: api, ID: uuid.New(), Created: time.Now(), Hash: hash, NodeID: nodeID} - // create the file immediately - if info.initDiskFile(filePath) != nil { - EncodeJSON(api.Backend, w, r, apiResponseDownloadStatus{APIStatus: DownloadResponseFileInvalid}) + // create the File immediately + if info.InitDiskFile(filePath) != nil { + EncodeJSON(api.Backend, w, r, ApiResponseDownloadStatus{APIStatus: DownloadResponseFileInvalid}) return } // add the download to the list - api.downloadAdd(info) + api.DownloadAdd(info) // start the download! go info.Start() - EncodeJSON(api.Backend, w, r, apiResponseDownloadStatus{APIStatus: DownloadResponseSuccess, ID: info.id, DownloadStatus: DownloadWaitMetadata}) + EncodeJSON(api.Backend, w, r, ApiResponseDownloadStatus{APIStatus: DownloadResponseSuccess, ID: info.ID, DownloadStatus: DownloadWaitMetadata}) } /* -apiDownloadStatus returns the status of an active download. +apiDownloadStatus returns the Status of an active download. -Request: GET /download/status?ID=[download ID] -Result: 200 with JSON structure apiResponseDownloadStatus +Request: GET /download/Status?ID=[download ID] +Result: 200 with JSON structure ApiResponseDownloadStatus */ func (api *WebapiInstance) apiDownloadStatus(w http.ResponseWriter, r *http.Request) { r.ParseForm() @@ -107,26 +107,26 @@ func (api *WebapiInstance) apiDownloadStatus(w http.ResponseWriter, r *http.Requ return } - info := api.downloadLookup(id) + info := api.DownloadLookup(id) if info == nil { - EncodeJSON(api.Backend, w, r, apiResponseDownloadStatus{APIStatus: DownloadResponseIDNotFound}) + EncodeJSON(api.Backend, w, r, ApiResponseDownloadStatus{APIStatus: DownloadResponseIDNotFound}) return } info.RLock() - response := apiResponseDownloadStatus{APIStatus: DownloadResponseSuccess, ID: info.id, DownloadStatus: info.status} + response := ApiResponseDownloadStatus{APIStatus: DownloadResponseSuccess, ID: info.ID, DownloadStatus: info.Status} - if info.status >= DownloadWaitSwarm { - response.File = info.file + if info.Status >= DownloadWaitSwarm { + response.File = info.File - response.Progress.TotalSize = info.file.Size + response.Progress.TotalSize = info.File.Size response.Progress.DownloadedSize = info.DiskFile.StoredSize - response.Progress.Percentage = math.Round(float64(info.DiskFile.StoredSize)/float64(info.file.Size)*100*100) / 100 + response.Progress.Percentage = math.Round(float64(info.DiskFile.StoredSize)/float64(info.File.Size)*100*100) / 100 } - if info.status >= DownloadActive { + if info.Status >= DownloadActive { response.Swarm.CountPeers = info.Swarm.CountPeers } @@ -136,12 +136,12 @@ func (api *WebapiInstance) apiDownloadStatus(w http.ResponseWriter, r *http.Requ } /* -apiDownloadAction pauses, resumes, and cancels a download. Once canceled, a new download has to be started if the file shall be downloaded. +apiDownloadAction pauses, resumes, and cancels a download. Once canceled, a new download has to be started if the File shall be downloaded. Only active downloads can be paused. While a download is in discovery phase (querying metadata, joining swarm), it can only be canceled. Action: 0 = Pause, 1 = Resume, 2 = Cancel. Request: GET /download/action?ID=[download ID]&action=[action] -Result: 200 with JSON structure apiResponseDownloadStatus (using APIStatus and DownloadStatus) +Result: 200 with JSON structure ApiResponseDownloadStatus (using APIStatus and DownloadStatus) */ func (api *WebapiInstance) apiDownloadAction(w http.ResponseWriter, r *http.Request) { r.ParseForm() @@ -152,9 +152,9 @@ func (api *WebapiInstance) apiDownloadAction(w http.ResponseWriter, r *http.Requ return } - info := api.downloadLookup(id) + info := api.DownloadLookup(id) if info == nil { - EncodeJSON(api.Backend, w, r, apiResponseDownloadStatus{APIStatus: DownloadResponseIDNotFound}) + EncodeJSON(api.Backend, w, r, ApiResponseDownloadStatus{APIStatus: DownloadResponseIDNotFound}) return } @@ -171,56 +171,56 @@ func (api *WebapiInstance) apiDownloadAction(w http.ResponseWriter, r *http.Requ apiStatus = info.Cancel() } - EncodeJSON(api.Backend, w, r, apiResponseDownloadStatus{APIStatus: apiStatus, ID: info.id, DownloadStatus: info.status}) + EncodeJSON(api.Backend, w, r, ApiResponseDownloadStatus{APIStatus: apiStatus, ID: info.ID, DownloadStatus: info.Status}) } // ---- download tracking ---- -type downloadInfo struct { - id uuid.UUID // Download ID - status int // Current status. See DownloadX. - sync.RWMutex // Mutext for changing the status +type DownloadInfo struct { + ID uuid.UUID // Download ID + Status int // Current Status. See DownloadX. + sync.RWMutex // Mutext for changing the Status // input - hash []byte // File hash - nodeID []byte // Node ID of the owner + Hash []byte // File Hash + NodeID []byte // Node ID of the owner // runtime data - created time.Time // When the download was created. - ended time.Time // When the download was finished (only status = DownloadFinished). + Created time.Time // When the download was Created. + Ended time.Time // When the download was finished (only Status = DownloadFinished). - file ApiFile // File metadata (only status >= DownloadWaitSwarm) + File ApiFile // File metadata (only Status >= DownloadWaitSwarm) - DiskFile struct { // Target file on disk to store downloaded data + DiskFile struct { // Target File on disk to store downloaded data Name string // File name - Handle *os.File // Target file (on disk) to store downloaded data - StoredSize uint64 // Count of bytes downloaded and stored in the file + Handle *os.File // Target File (on disk) to store downloaded data + StoredSize uint64 // Count of bytes downloaded and stored in the File } - Swarm struct { // Information about the swarm. Only valid for status >= DownloadActive. + Swarm struct { // Information about the swarm. Only valid for Status >= DownloadActive. CountPeers uint64 // Count of peers participating in the swarm. } // live connections, to be changed - peer *core.PeerInfo + Peer *core.PeerInfo - api *WebapiInstance - backend *core.Backend + Api *WebapiInstance + Backend *core.Backend } -func (api *WebapiInstance) downloadAdd(info *downloadInfo) { +func (api *WebapiInstance) DownloadAdd(info *DownloadInfo) { api.downloadsMutex.Lock() - api.downloads[info.id] = info + api.downloads[info.ID] = info api.downloadsMutex.Unlock() } -func (api *WebapiInstance) downloadDelete(id uuid.UUID) { +func (api *WebapiInstance) DownloadDelete(id uuid.UUID) { api.downloadsMutex.Lock() delete(api.downloads, id) api.downloadsMutex.Unlock() } -func (api *WebapiInstance) downloadLookup(id uuid.UUID) (info *downloadInfo) { +func (api *WebapiInstance) DownloadLookup(id uuid.UUID) (info *DownloadInfo) { api.downloadsMutex.Lock() info = api.downloads[id] api.downloadsMutex.Unlock() @@ -229,14 +229,14 @@ func (api *WebapiInstance) downloadLookup(id uuid.UUID) (info *downloadInfo) { // DeleteDefer deletes the download from the downloads list after the given duration. // It does not wait for the download to be finished. -func (info *downloadInfo) DeleteDefer(Duration time.Duration) { +func (info *DownloadInfo) DeleteDefer(Duration time.Duration) { go func() { <-time.After(Duration) - info.api.downloadDelete(info.id) + info.Api.DownloadDelete(info.ID) }() } -// DecodeBlake3Hash decodes a blake3 hash that is hex encoded +// DecodeBlake3Hash decodes a blake3 Hash that is hex encoded func DecodeBlake3Hash(text string) (hash []byte, valid bool) { hash, err := hex.DecodeString(text) return hash, err == nil && len(hash) == 256/8 diff --git a/webapi/File Detection.go b/webapi/File Detection.go index 8c4f2ae..0818f57 100644 --- a/webapi/File Detection.go +++ b/webapi/File Detection.go @@ -15,7 +15,7 @@ import ( "github.com/PeernetOfficial/core" ) -// PathToExtension translates a path to a file extension, if possible. It also returns the second file extension if there is one (relevant for files like "test.tar.gz"). +// PathToExtension translates a path to a File extension, if possible. It also returns the second File extension if there is one (relevant for files like "test.tar.gz"). func PathToExtension(Path string) (extension, extension2 string, valid bool) { _, fileA := path.Split(Path) @@ -143,7 +143,7 @@ func HTTPContentTypeToCore(httpContentType string) (fileType, fileFormat uint16) } } -// FileDataToHTTPContentType returns the HTTP content type based on the initial file data. It reads the first 512 bytes of the file. +// FileDataToHTTPContentType returns the HTTP content type based on the initial File data. It reads the first 512 bytes of the File. func FileDataToHTTPContentType(Path string) (httpContentType string, err error) { file, err := os.Open(Path) if err != nil { @@ -172,10 +172,10 @@ func FileDataToHTTPContentType(Path string) (httpContentType string, err error) return httpContentType, nil } -// FileDetectType detects the File Type and File Format of a file. It uses the extension if available, otherwise the file data, for detection. +// FileDetectType detects the File Type and File Format of a File. It uses the extension if available, otherwise the File data, for detection. func FileDetectType(Path string) (fileType, fileFormat uint16, err error) { - // If a file extension is available, use that to detect the file type and file format. - // Otherwise, use the initial file data for detection. + // If a File extension is available, use that to detect the File type and File format. + // Otherwise, use the initial File data for detection. if ext1, _, valid := PathToExtension(Path); valid { fileType, fileFormat = FileTranslateExtension(ext1) return fileType, fileFormat, nil @@ -191,16 +191,16 @@ func FileDetectType(Path string) (fileType, fileFormat uint16, err error) { } type apiResponseFileFormat struct { - Status int `json:"status"` // Status: 0 = Success, 1 = Error reading file + Status int `json:"Status"` // Status: 0 = Success, 1 = Error reading File FileType uint16 `json:"filetype"` // File Type. FileFormat uint16 `json:"fileformat"` // File Format. } /* -apiFileFormat detects the file type and file format of the specified file. -It will primarily use the file extension for detection. If unavailable, it uses the first 512 bytes of the file data to detect the type. +apiFileFormat detects the File type and File format of the specified File. +It will primarily use the File extension for detection. If unavailable, it uses the first 512 bytes of the File data to detect the type. -Request: GET /file/format?path=[file path on disk] +Request: GET /File/format?path=[File path on disk] Result: 200 with JSON structure apiResponseFileFormat */ func (api *WebapiInstance) apiFileFormat(w http.ResponseWriter, r *http.Request) { diff --git a/webapi/File IO.go b/webapi/File IO.go index 58ee00a..2a0e796 100644 --- a/webapi/File IO.go +++ b/webapi/File IO.go @@ -20,26 +20,26 @@ import ( ) /* -apiFileRead reads a file immediately from a remote peer. Use the /download functions to download a file. +apiFileRead reads a File immediately from a remote Peer. Use the /download functions to download a File. This endpoint supports the Range, Content-Range and Content-Length headers. Multipart ranges are not supported and result in HTTP 400. -Instead of providing the node ID, the peer ID is also accepted in the &node= parameter. -The default timeout for connecting to the peer is 10 seconds. +Instead of providing the node ID, the Peer ID is also accepted in the &node= parameter. +The default timeout for connecting to the Peer is 10 seconds. -Request: GET /file/read?hash=[hash]&node=[node ID] +Request: GET /File/read?Hash=[Hash]&node=[node ID] Optional: &offset=[offset]&limit=[limit] or via Range header. Optional: &timeout=[seconds] Response: 200 with the content 206 with partial content 400 if the parameters are invalid - 404 if the file was not found or other error on transfer initiate - 502 if unable to find or connect to the remote peer in time + 404 if the File was not found or other error on transfer initiate + 502 if unable to find or connect to the remote Peer in time */ func (api *WebapiInstance) apiFileRead(w http.ResponseWriter, r *http.Request) { r.ParseForm() var err error // validate hashes (must be blake3) and other input - fileHash, valid1 := DecodeBlake3Hash(r.Form.Get("hash")) + fileHash, valid1 := DecodeBlake3Hash(r.Form.Get("Hash")) nodeID, valid2 := DecodeBlake3Hash(r.Form.Get("node")) publicKey, err3 := core.PublicKeyFromPeerID(r.Form.Get("node")) if !valid1 || (!valid2 && err3 != nil) { @@ -68,12 +68,12 @@ func (api *WebapiInstance) apiFileRead(w http.ResponseWriter, r *http.Request) { offset = ranges[0].start } - // Is the file available in the local warehouse? In that case requesting it from the remote is unnecessary. + // Is the File available in the local warehouse? In that case requesting it from the remote is unnecessary. if serveFileFromWarehouse(api.Backend, w, fileHash, uint64(offset), uint64(limit), ranges) { return } - // try connecting via node ID or peer ID? + // try connecting via node ID or Peer ID? var peer *core.PeerInfo if valid2 { @@ -103,10 +103,10 @@ func (api *WebapiInstance) apiFileRead(w http.ResponseWriter, r *http.Request) { io.Copy(w, io.LimitReader(reader, int64(transferSize))) } -// serveFileFromWarehouse serves the file from the warehouse. If it is not available, it returns false and does not use the writer. -// Limit is optional, 0 means the entire file. +// serveFileFromWarehouse serves the File from the warehouse. If it is not available, it returns false and does not use the writer. +// Limit is optional, 0 means the entire File. func serveFileFromWarehouse(backend *core.Backend, w http.ResponseWriter, fileHash []byte, offset, limit uint64, ranges []HTTPRange) (valid bool) { - // Check if the file is available in the local warehouse. + // Check if the File is available in the local warehouse. _, fileSize, status, _ := backend.UserWarehouse.FileExists(fileHash) if status != warehouse.StatusOK { return false @@ -127,32 +127,32 @@ func serveFileFromWarehouse(backend *core.Backend, w http.ResponseWriter, fileHa status, _, _ = backend.UserWarehouse.ReadFile(fileHash, int64(offset), int64(limit), w) - // StatusErrorReadFile must be considered success, since parts of the file may have been transferred already and recovery is not possible. + // StatusErrorReadFile must be considered success, since parts of the File may have been transferred already and recovery is not possible. return status == warehouse.StatusErrorReadFile || status == warehouse.StatusOK } /* -apiFileView is similar to /file/read but but provides a format parameter. It sets the Content-Type and Accept-Ranges headers. +apiFileView is similar to /File/read but but provides a format parameter. It sets the Content-Type and Accept-Ranges headers. This endpoint supports the Range, Content-Range and Content-Length headers. Multipart ranges are not supported and result in HTTP 400. -Instead of providing the node ID, the peer ID is also accepted in the &node= parameter. -The default timeout for connecting to the peer is 10 seconds. +Instead of providing the node ID, the Peer ID is also accepted in the &node= parameter. +The default timeout for connecting to the Peer is 10 seconds. Formats: 14 = Video -Request: GET /file/view?hash=[hash]&node=[node ID]&format=[format] +Request: GET /File/view?Hash=[Hash]&node=[node ID]&format=[format] Optional: &offset=[offset]&limit=[limit] or via Range header. Optional: &timeout=[seconds] Response: 200 with the content 206 with partial content 400 if the parameters are invalid - 404 if the file was not found or other error on transfer initiate - 502 if unable to find or connect to the remote peer in time + 404 if the File was not found or other error on transfer initiate + 502 if unable to find or connect to the remote Peer in time */ func (api *WebapiInstance) apiFileView(w http.ResponseWriter, r *http.Request) { r.ParseForm() var err error // validate hashes (must be blake3) and other input - fileHash, valid1 := DecodeBlake3Hash(r.Form.Get("hash")) + fileHash, valid1 := DecodeBlake3Hash(r.Form.Get("Hash")) nodeID, valid2 := DecodeBlake3Hash(r.Form.Get("node")) publicKey, err3 := core.PublicKeyFromPeerID(r.Form.Get("node")) if !valid1 || (!valid2 && err3 != nil) { @@ -191,14 +191,14 @@ func (api *WebapiInstance) apiFileView(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "video/mp4") } - // Is the file available in the local warehouse? In that case requesting it from the remote is unnecessary. + // Is the File available in the local warehouse? In that case requesting it from the remote is unnecessary. if !localCacheDisable { if serveFileFromWarehouse(api.Backend, w, fileHash, uint64(offset), uint64(limit), ranges) { return } } - // try connecting via node ID or peer ID? + // try connecting via node ID or Peer ID? var peer *core.PeerInfo if valid2 { @@ -228,13 +228,13 @@ func (api *WebapiInstance) apiFileView(w http.ResponseWriter, r *http.Request) { io.Copy(w, io.LimitReader(reader, int64(transferSize))) } -// PeerConnectPublicKey attempts to connect to the peer specified by its public key (= peer ID). +// PeerConnectPublicKey attempts to connect to the Peer specified by its public key (= Peer ID). func PeerConnectPublicKey(backend *core.Backend, publicKey *btcec.PublicKey, timeout time.Duration) (peer *core.PeerInfo, err error) { if publicKey == nil { return nil, errors.New("invalid public key") } - // First look up in the peer list. + // First look up in the Peer list. if peer = backend.PeerlistLookup(publicKey); peer != nil { return peer, nil } @@ -246,7 +246,7 @@ func PeerConnectPublicKey(backend *core.Backend, publicKey *btcec.PublicKey, tim } // otherwise not found :( - return nil, errors.New("peer not found") + return nil, errors.New("Peer not found") } // PeerConnectNode tries to connect via the node ID @@ -261,18 +261,18 @@ func PeerConnectNode(backend *core.Backend, nodeID []byte, timeout time.Duration } // otherwise not found :( - return nil, errors.New("peer not found") + return nil, errors.New("Peer not found") } -// FileStartReader providers a reader to a remote file. The reader must be closed by the caller. -// File Size is the full file size reported by the remote peer, regardless of the requested offset and limit. Limit is optional (0 means the entire file). +// FileStartReader providers a reader to a remote File. The reader must be closed by the caller. +// File Size is the full File size reported by the remote Peer, regardless of the requested offset and limit. Limit is optional (0 means the entire File). // Transfer Size is the size in bytes that is actually going to be transferred. The reader should be closed after reading that amount. -// The optional cancelChan can be used to stop the file transfer at any point. +// The optional cancelChan can be used to stop the File transfer at any point. func FileStartReader(peer *core.PeerInfo, hash []byte, offset, limit uint64, cancelChan <-chan struct{}) (reader io.ReadCloser, fileSize, transferSize uint64, err error) { if peer == nil { - return nil, 0, 0, errors.New("peer not provided") + return nil, 0, 0, errors.New("Peer not provided") } else if !peer.IsConnectionActive() { - return nil, 0, 0, errors.New("no valid connection to peer") + return nil, 0, 0, errors.New("no valid connection to Peer") } udtConn, virtualConn, err := peer.FileTransferRequestUDT(hash, offset, limit) @@ -298,10 +298,10 @@ func FileStartReader(peer *core.PeerInfo, hash []byte, offset, limit uint64, can return udtConn, fileSize, transferSize, nil } -// FileReadAll downloads the file from the peer. -// This function should only be used for testing or as a basis to fork. The caller should develop a custom download function that handles timeouts and excessive file sizes. -// It allocates whatever size is reported by the remote peer. This could lead to an out of memory crash. -// This function is blocking and may take a long time depending on the remote peer and the network connection. +// FileReadAll downloads the File from the Peer. +// This function should only be used for testing or as a basis to fork. The caller should develop a custom download function that handles timeouts and excessive File sizes. +// It allocates whatever size is reported by the remote Peer. This could lead to an out of memory crash. +// This function is blocking and may take a long time depending on the remote Peer and the network connection. func FileReadAll(peer *core.PeerInfo, hash []byte) (data []byte, err error) { reader, _, transferSize, err := FileStartReader(peer, hash, 0, 0, nil) if err != nil { @@ -313,7 +313,7 @@ func FileReadAll(peer *core.PeerInfo, hash []byte) (data []byte, err error) { data = make([]byte, transferSize) // Warning: This could lead to an out of memory crash. _, err = reader.Read(data) - // Note: This function does not verify if the returned data matches the hash and expected size. + // Note: This function does not verify if the returned data matches the Hash and expected size. return data, err } diff --git a/webapi/File.go b/webapi/File.go index 6199413..52f3fea 100644 --- a/webapi/File.go +++ b/webapi/File.go @@ -29,18 +29,18 @@ type ApiFileMetadata struct { Number uint64 `json:"number"` // Number } -// ApiFile is the metadata of a file published on the blockchain +// ApiFile is the metadata of a File published on the blockchain type ApiFile struct { ID uuid.UUID `json:"ID"` // Unique ID. - Hash []byte `json:"hash"` // Blake3 hash of the file data + Hash []byte `json:"Hash"` // Blake3 Hash of the File data Type uint8 `json:"type"` // File Type. For example audio or document. See TypeX. - Format uint16 `json:"format"` // File Format. This is more granular, for example PDF or Word file. See FormatX. - Size uint64 `json:"size"` // Size of the file + Format uint16 `json:"format"` // File Format. This is more granular, for example PDF or Word File. See FormatX. + Size uint64 `json:"size"` // Size of the File Folder string `json:"folder"` // Folder, optional - Name string `json:"name"` // Name of the file + Name string `json:"name"` // Name of the File Description string `json:"description"` // Description. This is expected to be multiline and contain hashtags! Date time.Time `json:"date"` // Date shared - NodeID []byte `json:"nodeid"` // Node ID, owner of the file. Read only. + NodeID []byte `json:"nodeid"` // Node ID, owner of the File. Read only. Metadata []ApiFileMetadata `json:"metadata"` // Additional metadata. } @@ -118,17 +118,17 @@ func BlockRecordFileFromAPI(input ApiFile) (output blockchain.BlockRecordFile) { // ApiBlockAddFiles contains a list of files from the blockchain type ApiBlockAddFiles struct { Files []ApiFile `json:"files"` // List of files - Status int `json:"status"` // Status of the operation, only used when this structure is returned from the API. + Status int `json:"Status"` // Status of the operation, only used when this structure is returned from the API. } /* -apiBlockchainFileAdd adds a file with the provided information to the blockchain. -Each file must be already stored in the Warehouse (virtual folders are exempt). -If any file is not stored in the Warehouse, the function aborts with the status code StatusNotInWarehouse. -If the block record encoding fails for any file, this function aborts with the status code StatusCorruptBlockRecord. +apiBlockchainFileAdd adds a File with the provided information to the blockchain. +Each File must be already stored in the Warehouse (virtual folders are exempt). +If any File is not stored in the Warehouse, the function aborts with the Status code StatusNotInWarehouse. +If the block record encoding fails for any File, this function aborts with the Status code StatusCorruptBlockRecord. In case the function aborts, the blockchain remains unchanged. -Request: POST /blockchain/file/add with JSON structure ApiBlockAddFiles +Request: POST /blockchain/File/add with JSON structure ApiBlockAddFiles Response: 200 with JSON structure apiBlockchainBlockStatus 400 if invalid input */ @@ -149,7 +149,7 @@ func (api *WebapiInstance) apiBlockchainFileAdd(w http.ResponseWriter, r *http.R file.ID = uuid.New() } - // Verify that the file exists in the warehouse. Folders are exempt from this check as they are only virtual. + // Verify that the File exists in the warehouse. Folders are exempt from this check as they are only virtual. if !file.IsVirtualFolder() { if _, err := warehouse.ValidateHash(file.Hash); err != nil { http.Error(w, "", http.StatusBadRequest) @@ -184,7 +184,7 @@ func (api *WebapiInstance) apiBlockchainFileAdd(w http.ResponseWriter, r *http.R /* apiBlockchainFileList lists all files stored on the blockchain. -Request: GET /blockchain/file/list +Request: GET /blockchain/File/list Response: 200 with JSON structure ApiBlockAddFiles */ func (api *WebapiInstance) apiBlockchainFileList(w http.ResponseWriter, r *http.Request) { @@ -203,9 +203,9 @@ func (api *WebapiInstance) apiBlockchainFileList(w http.ResponseWriter, r *http. /* apiBlockchainFileDelete deletes files with the provided IDs. Other fields are ignored. -It will automatically delete the file in the Warehouse if there are no other references. +It will automatically delete the File in the Warehouse if there are no other references. -Request: POST /blockchain/file/delete with JSON structure ApiBlockAddFiles +Request: POST /blockchain/File/delete with JSON structure ApiBlockAddFiles Response: 200 with JSON structure apiBlockchainBlockStatus */ func (api *WebapiInstance) apiBlockchainFileDelete(w http.ResponseWriter, r *http.Request) { @@ -237,7 +237,7 @@ func (api *WebapiInstance) apiBlockchainFileDelete(w http.ResponseWriter, r *htt /* apiBlockchainSelfUpdateFile updates files that are already published on the blockchain. -Request: POST /blockchain/file/update with JSON structure ApiBlockAddFiles +Request: POST /blockchain/File/update with JSON structure ApiBlockAddFiles Response: 200 with JSON structure apiBlockchainBlockStatus 400 if invalid input */ @@ -258,7 +258,7 @@ func (api *WebapiInstance) apiBlockchainFileUpdate(w http.ResponseWriter, r *htt return } - // Verify that the file exists in the warehouse. Folders are exempt from this check as they are only virtual. + // Verify that the File exists in the warehouse. Folders are exempt from this check as they are only virtual. if !file.IsVirtualFolder() { if _, err := warehouse.ValidateHash(file.Hash); err != nil { http.Error(w, "", http.StatusBadRequest) @@ -312,7 +312,7 @@ func (info *ApiFileMetadata) GetNumber() uint64 { return info.Number } -// IsVirtualFolder returns true if the file is a virtual folder +// IsVirtualFolder returns true if the File is a virtual folder func (file *ApiFile) IsVirtualFolder() bool { return file.Type == core.TypeFolder && file.Format == core.FormatFolder } @@ -324,7 +324,7 @@ func SetFileMerkleInfo(backend *core.Backend, file *blockchain.BlockRecordFile) file.MerkleRootHash = file.Hash file.FragmentSize = merkle.MinimumFragmentSize } else { - // Get the information from the Warehouse .merkle companion file. + // Get the information from the Warehouse .merkle companion File. tree, status, _ := backend.UserWarehouse.ReadMerkleTree(file.Hash, true) if status != warehouse.StatusOK { return false diff --git a/webapi/HTTP Range.go b/webapi/HTTP Range.go index 6dbf473..dc2613f 100644 --- a/webapi/HTTP Range.go +++ b/webapi/HTTP Range.go @@ -1,109 +1,109 @@ -/* -File Name: HTTP Range.go -Copyright: 2021 Peernet Foundation s.r.o. -Author: Peter Kleissner -*/ - -package webapi - -import ( - "errors" - "net/http" - "net/textproto" - "strconv" - "strings" -) - -// Fork from https://golang.org/src/net/http/fs.go - -// HTTPRange represents an HTTP range -type HTTPRange struct { - start, length int -} - -// ParseRangeHeader parses a Range header string as per RFC 7233. -func ParseRangeHeader(s string, size int, noMultiRange bool) ([]HTTPRange, error) { - if s == "" { - return nil, nil // header not present - } - const b = "bytes=" - if !strings.HasPrefix(s, b) { - return nil, errors.New("invalid range") - } - var ranges []HTTPRange - for _, ra := range strings.Split(s[len(b):], ",") { - ra = textproto.TrimString(ra) - if ra == "" { - continue - } - i := strings.Index(ra, "-") - if i < 0 { - return nil, errors.New("invalid range") - } - start, end := textproto.TrimString(ra[:i]), textproto.TrimString(ra[i+1:]) - var r HTTPRange - if start == "" { - if size < 0 { - return nil, errors.New("range start relative to end not supported") - } - // If no start is specified, end specifies the - // range start relative to the end of the file. - i, err := strconv.ParseInt(end, 10, 64) - if err != nil { - return nil, errors.New("invalid range") - } - if int(i) > size { - i = int64(size) - } - r.start = size - int(i) - r.length = size - r.start - } else { - i, err := strconv.ParseInt(start, 10, 64) - if err != nil || i < 0 { - return nil, errors.New("invalid range") - } - if size > 0 && int(i) >= size { - // If the range begins after the size of the content, then it does not overlap. -> always return error. - return nil, errors.New("start out of range") - } - r.start = int(i) - if end == "" { - // If no end is specified, range extends to end of the file. - if size < 0 { - //return nil, errors.New("open range not supported") - r.length = -1 - } else { - r.length = size - r.start - } - } else { - i, err := strconv.ParseInt(end, 10, 64) - if err != nil || r.start > int(i) { - return nil, errors.New("invalid range") - } - if size > 0 && int(i) >= size { - i = int64(size - 1) - } - r.length = int(i) - r.start + 1 - } - } - ranges = append(ranges, r) - if noMultiRange && len(ranges) > 1 { - return nil, errors.New("multiple ranges not supported") - } - } - return ranges, nil -} - -// setContentLengthRangeHeader sets the appropriate Content-Length and Content-Range headers -func setContentLengthRangeHeader(w http.ResponseWriter, offset, transferSize, fileSize uint64, ranges []HTTPRange) { - // Set the Content-Length header, always to the actual size of transferred data. - w.Header().Set("Content-Length", strconv.FormatUint(transferSize, 10)) - - // Set the Content-Range header if needed. - if len(ranges) == 1 { - w.Header().Set("Content-Range", "bytes "+strconv.FormatUint(offset, 10)+"-"+strconv.FormatUint(offset+transferSize-1, 10)+"/"+strconv.FormatUint(fileSize, 10)) - w.WriteHeader(http.StatusPartialContent) - } else { - w.WriteHeader(http.StatusOK) - } -} +/* +File Name: HTTP Range.go +Copyright: 2021 Peernet Foundation s.r.o. +Author: Peter Kleissner +*/ + +package webapi + +import ( + "errors" + "net/http" + "net/textproto" + "strconv" + "strings" +) + +// Fork from https://golang.org/src/net/http/fs.go + +// HTTPRange represents an HTTP range +type HTTPRange struct { + start, length int +} + +// ParseRangeHeader parses a Range header string as per RFC 7233. +func ParseRangeHeader(s string, size int, noMultiRange bool) ([]HTTPRange, error) { + if s == "" { + return nil, nil // header not present + } + const b = "bytes=" + if !strings.HasPrefix(s, b) { + return nil, errors.New("invalid range") + } + var ranges []HTTPRange + for _, ra := range strings.Split(s[len(b):], ",") { + ra = textproto.TrimString(ra) + if ra == "" { + continue + } + i := strings.Index(ra, "-") + if i < 0 { + return nil, errors.New("invalid range") + } + start, end := textproto.TrimString(ra[:i]), textproto.TrimString(ra[i+1:]) + var r HTTPRange + if start == "" { + if size < 0 { + return nil, errors.New("range start relative to end not supported") + } + // If no start is specified, end specifies the + // range start relative to the end of the File. + i, err := strconv.ParseInt(end, 10, 64) + if err != nil { + return nil, errors.New("invalid range") + } + if int(i) > size { + i = int64(size) + } + r.start = size - int(i) + r.length = size - r.start + } else { + i, err := strconv.ParseInt(start, 10, 64) + if err != nil || i < 0 { + return nil, errors.New("invalid range") + } + if size > 0 && int(i) >= size { + // If the range begins after the size of the content, then it does not overlap. -> always return error. + return nil, errors.New("start out of range") + } + r.start = int(i) + if end == "" { + // If no end is specified, range extends to end of the File. + if size < 0 { + //return nil, errors.New("open range not supported") + r.length = -1 + } else { + r.length = size - r.start + } + } else { + i, err := strconv.ParseInt(end, 10, 64) + if err != nil || r.start > int(i) { + return nil, errors.New("invalid range") + } + if size > 0 && int(i) >= size { + i = int64(size - 1) + } + r.length = int(i) - r.start + 1 + } + } + ranges = append(ranges, r) + if noMultiRange && len(ranges) > 1 { + return nil, errors.New("multiple ranges not supported") + } + } + return ranges, nil +} + +// setContentLengthRangeHeader sets the appropriate Content-Length and Content-Range headers +func setContentLengthRangeHeader(w http.ResponseWriter, offset, transferSize, fileSize uint64, ranges []HTTPRange) { + // Set the Content-Length header, always to the actual size of transferred data. + w.Header().Set("Content-Length", strconv.FormatUint(transferSize, 10)) + + // Set the Content-Range header if needed. + if len(ranges) == 1 { + w.Header().Set("Content-Range", "bytes "+strconv.FormatUint(offset, 10)+"-"+strconv.FormatUint(offset+transferSize-1, 10)+"/"+strconv.FormatUint(fileSize, 10)) + w.WriteHeader(http.StatusPartialContent) + } else { + w.WriteHeader(http.StatusOK) + } +} diff --git a/webapi/Profile.go b/webapi/Profile.go index 1627be8..9682c0a 100644 --- a/webapi/Profile.go +++ b/webapi/Profile.go @@ -16,7 +16,7 @@ import ( // apiProfileData contains profile metadata stored on the blockchain. Any data is treated as untrusted and unverified by default. type apiProfileData struct { Fields []apiBlockRecordProfile `json:"fields"` // All fields - Status int `json:"status"` // Status of the operation, only used when this structure is returned from the API. See blockchain.StatusX. + Status int `json:"Status"` // Status of the operation, only used when this structure is returned from the API. See blockchain.StatusX. } // apiBlockRecordProfile provides information about the end user. Note that all profile data is arbitrary and shall be considered untrusted and unverified. diff --git a/webapi/Search Dispatch.go b/webapi/Search Dispatch.go index 94a8401..b6c801d 100644 --- a/webapi/Search Dispatch.go +++ b/webapi/Search Dispatch.go @@ -48,7 +48,7 @@ resultLoop: continue } - // Deduplicate based on file hash from the same peer. + // Deduplicate based on File Hash from the same Peer. for n := range job.AllFiles { if bytes.Equal(job.AllFiles[n].Hash, file.Hash) && bytes.Equal(job.AllFiles[n].NodeID, file.NodeID) { continue resultLoop diff --git a/webapi/Search Job.go b/webapi/Search Job.go index 03f3784..f993ff9 100644 --- a/webapi/Search Job.go +++ b/webapi/Search Job.go @@ -23,8 +23,8 @@ type SearchFilter struct { FileType int // File type such as binary, text document etc. See core.TypeX. -1 = not used. FileFormat int // File format such as PDF, Word, Ebook, etc. See core.FormatX. -1 = not used. Sort int // Sort order. See SortX. - SizeMin int // Min file size in bytes. -1 = not used. - SizeMax int // Max file size in bytes. -1 = not used. + SizeMin int // Min File size in bytes. -1 = not used. + SizeMax int // Max File size in bytes. -1 = not used. } // SearchJob is a collection of search jobs @@ -34,7 +34,7 @@ type SearchJob struct { timeout time.Duration // timeout set for all searches maxResult int // max results user-facing. - filtersStart SearchFilter // Filters when starting the search. They cannot be changed later on. Any incoming file is checked against them, even if there are different runtime filters. + filtersStart SearchFilter // Filters when starting the search. They cannot be changed later on. Any incoming File is checked against them, even if there are different runtime filters. filtersRuntime SearchFilter // Runtime Filters. They allow filtering results after they were received. // File statistics (filters are ignored) of returned results. Map value is always count of files. @@ -48,7 +48,7 @@ type SearchJob struct { // -- result data -- - // Status indicates the overall search status. This will be removed later when relying on search clients. + // Status indicates the overall search Status. This will be removed later when relying on search clients. Status int // runtime data @@ -65,7 +65,7 @@ type SearchJob struct { // List of all files. Does not change based on sorting or runtime filters. This list only gets expanded. AllFiles []*ApiFile - ResultSync sync.Mutex // ResultSync ensures unique access to the file results + ResultSync sync.Mutex // ResultSync ensures unique access to the File results currentOffset int // for always getting the next results } @@ -235,7 +235,7 @@ func (job *SearchJob) RuntimeFilter(Filter SearchFilter) { } } -// isFileFiltered returns true if the file conforms to the runtime filter. If there is no runtime filter, it always returns true. +// isFileFiltered returns true if the File conforms to the runtime filter. If there is no runtime filter, it always returns true. func (job *SearchJob) isFileFiltered(file *ApiFile) bool { if job.filtersRuntime.FileType >= 0 && file.Type != uint8(job.filtersRuntime.FileType) { return false @@ -245,7 +245,7 @@ func (job *SearchJob) isFileFiltered(file *ApiFile) bool { return false } - // Note: If the date is not available in the file, it will be filtered out. Since this is the mapped Shared Date this should normally not occur though. + // Note: If the date is not available in the File, it will be filtered out. Since this is the mapped Shared Date this should normally not occur though. if job.filtersRuntime.IsDates && (file.Date.IsZero() || file.Date.Before(job.filtersRuntime.DateFrom) || file.Date.After(job.filtersRuntime.DateTo)) { return false } @@ -302,7 +302,7 @@ func (job *SearchJob) IsSearchResults() bool { return len(job.Files) > 0 || !job.IsTerminated() } -// isFileReceived checks if a file was already received, preventing double results +// isFileReceived checks if a File was already received, preventing double results func (job *SearchJob) isFileReceived(id uuid.UUID) (exists bool) { // Future: A map would be likely faster than iterating over all results. for m := range job.AllFiles { @@ -394,8 +394,8 @@ type SearchStatisticRecord struct { // SearchStatisticData contains statistics on search results. type SearchStatisticData struct { Date []SearchStatisticRecordDay `json:"date"` // Files per date - FileType []SearchStatisticRecord `json:"filetype"` // Files per file type - FileFormat []SearchStatisticRecord `json:"fileformat"` // Files per file format + FileType []SearchStatisticRecord `json:"filetype"` // Files per File type + FileFormat []SearchStatisticRecord `json:"fileformat"` // Files per File format Total int `json:"total"` // Total count of files } @@ -429,7 +429,7 @@ func (job *SearchJob) statsAdd(files ...*ApiFile) { defer job.stats.Unlock() for _, file := range files { - // Use file's Date field if available. + // Use File's Date field if available. if !file.Date.IsZero() { // Files per day date := file.Date.Truncate(24 * time.Hour) diff --git a/webapi/Search.go b/webapi/Search.go index 5b35daa..ac1e531 100644 --- a/webapi/Search.go +++ b/webapi/Search.go @@ -34,8 +34,8 @@ type SearchRequest struct { 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. + 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 @@ -56,12 +56,12 @@ const ( // 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 + 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 + 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. } @@ -69,7 +69,7 @@ type SearchResult struct { // 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 + Status int `json:"Status"` // Status: 0 = Success IsTerminated bool `json:"terminated"` // Whether the search is terminated, meaning that statistics won't change } @@ -119,11 +119,11 @@ Optional parameters: &filetype=[File Type] &fileformat=[File Format] &from=[Date From]&to=[Date To] - &sizemin=[Minimum file size] - &sizemax=[Maximum file size] + &sizemin=[Minimum File size] + &sizemax=[Maximum File size] &sort=[sort order] &offset=[absolute offset] with &limit=[records] to get items pagination style. Returned items (and ones before) are automatically frozen. -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) { r.ParseForm() @@ -176,7 +176,7 @@ func (api *WebapiInstance) apiSearchResult(w http.ResponseWriter, r *http.Reques result.Files = append(result.Files, *resultFiles[n]) } - // set the status + // set the Status if len(result.Files) > 0 { if job.IsSearchResults() { result.Status = 0 // 0 = Success with results @@ -323,7 +323,7 @@ func (api *WebapiInstance) apiSearchTerminate(w http.ResponseWriter, r *http.Req apiSearchStatistic returns search result statistics. Statistics are always calculated over all results, regardless of any applied runtime filters. 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) { r.ParseForm() @@ -346,11 +346,11 @@ func (api *WebapiInstance) apiSearchStatistic(w http.ResponseWriter, r *http.Req } /* -apiExplore returns recently shared files in Peernet. Results are returned in real-time. The file type is an optional filter. See TypeX. +apiExplore returns recently shared files in Peernet. Results are returned in real-time. The File type is an optional filter. See TypeX. Special type -2 = Binary, Compressed, Container, Executable. This special type includes everything except Documents, Video, Audio, Ebooks, Picture, Text. -Request: GET /explore?limit=[max records]&type=[file type]&offset=[offset] -Result: 200 with JSON structure SearchResult. Check the field status. +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() diff --git a/webapi/Shared Recent.go b/webapi/Shared Recent.go index a28d7d2..575be52 100644 --- a/webapi/Shared Recent.go +++ b/webapi/Shared Recent.go @@ -18,7 +18,7 @@ func (api *WebapiInstance) queryRecentShared(backend *core.Backend, fileType int limitPeer = 1 } - // 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() // Files from peers exceeding the limit. It is used if from all peers the total limit is not reached. @@ -48,7 +48,7 @@ func (api *WebapiInstance) queryRecentShared(backend *core.Backend, fileType int file.Tags = append(file.Tags, blockchain.TagFromText(blockchain.TagSharedByGeoIP, sharedByGeoIP)) } - // found a new file! append. + // found a new File! append. if filesFromPeer < limitPeer { filesFromPeer++ @@ -77,7 +77,7 @@ func (api *WebapiInstance) queryRecentShared(backend *core.Backend, fileType int 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 { if fileType == -1 { return true diff --git a/webapi/Status.go b/webapi/Status.go index 8825289..c188865 100644 --- a/webapi/Status.go +++ b/webapi/Status.go @@ -19,17 +19,17 @@ func apiTest(w http.ResponseWriter, r *http.Request) { } type apiResponseStatus struct { - Status int `json:"status"` // Status code: 0 = Ok. + Status int `json:"Status"` // Status code: 0 = Ok. IsConnected bool `json:"isconnected"` // Whether connected to Peernet. - CountPeerList int `json:"countpeerlist"` // Count of peers in the peer list. Note that this contains peers that are considered inactive, but have not yet been removed from the list. + CountPeerList int `json:"countpeerlist"` // Count of peers in the Peer list. Note that this contains peers that are considered inactive, but have not yet been removed from the list. CountNetwork int `json:"countnetwork"` // Count of total peers in the network. // This is usually a higher number than CountPeerList, which just represents the current number of connected peers. // The CountNetwork number is going to be queried from root peers which may or may not have a limited view. } /* -apiStatus returns the current connectivity status to the network -Request: GET /status +apiStatus returns the current connectivity Status to the network +Request: GET /Status Result: 200 with JSON structure Status */ func (api *WebapiInstance) apiStatus(w http.ResponseWriter, r *http.Request) { @@ -46,7 +46,7 @@ func (api *WebapiInstance) apiStatus(w http.ResponseWriter, r *http.Request) { type apiResponsePeerSelf struct { PeerID string `json:"peerid"` // Peer ID. This is derived from the public in compressed form. - NodeID string `json:"nodeid"` // Node ID. This is the blake3 hash of the peer ID and used in the DHT. + NodeID string `json:"nodeid"` // Node ID. This is the blake3 Hash of the Peer ID and used in the DHT. } /* @@ -85,10 +85,10 @@ func (api *WebapiInstance) apiAccountDelete(w http.ResponseWriter, r *http.Reque /* apiStatusPeers returns the information about peers currently connected. -The GeoIP information may not alawys be available, for example if the GeoIP file is not available or the mapping from IP address to location is not available. +The GeoIP information may not alawys be available, for example if the GeoIP File is not available or the mapping from IP address to location is not available. Peers that are connected only via local network will not have a geo location. -Request: GET /status/peers +Request: GET /Status/peers Result: 200 with JSON array apiResponsePeerInfo */ func (api *WebapiInstance) apiStatusPeers(w http.ResponseWriter, r *http.Request) { @@ -117,10 +117,10 @@ func (api *WebapiInstance) apiStatusPeers(w http.ResponseWriter, r *http.Request type apiResponsePeerInfo struct { PeerID []byte `json:"peerid"` // Peer ID. This is derived from the public in compressed form. - NodeID []byte `json:"nodeid"` // Node ID. This is the blake3 hash of the peer ID and used in the DHT. + NodeID []byte `json:"nodeid"` // Node ID. This is the blake3 Hash of the Peer ID and used in the DHT. GeoIP string `json:"geoip"` // GeoIP location as "Latitude,Longitude" CSV format. Empty if location not available. UserAgent string `json:"useragent"` // User Agent. - IsRoot bool `json:"isroot"` // If the peer is a root peer. + IsRoot bool `json:"isroot"` // If the Peer is a root Peer. BlockchainHeight uint64 `json:"blockchainheight"` // Blockchain height BlockchainVersion uint64 `json:"blockchainversion"` // Blockchain version } diff --git a/webapi/Warehouse.go b/webapi/Warehouse.go index 93caa6b..68d5064 100644 --- a/webapi/Warehouse.go +++ b/webapi/Warehouse.go @@ -13,31 +13,31 @@ import ( "github.com/PeernetOfficial/core/warehouse" ) -// WarehouseResult is the response to creating a new file in the warehouse +// WarehouseResult is the response to creating a new File in the warehouse type WarehouseResult struct { - Status int `json:"status"` // See warehouse.StatusX. - Hash []byte `json:"hash"` // Hash of the file. + Status int `json:"Status"` // See warehouse.StatusX. + Hash []byte `json:"Hash"` // Hash of the File. } /* -apiWarehouseCreateFile creates a file in the warehouse. +apiWarehouseCreateFile creates a File in the warehouse. -Request: POST /warehouse/create with raw data to create as new file +Request: POST /warehouse/create with raw data to create as new File Response: 200 with JSON structure WarehouseResult */ func (api *WebapiInstance) apiWarehouseCreateFile(w http.ResponseWriter, r *http.Request) { hash, status, err := api.Backend.UserWarehouse.CreateFile(r.Body, 0) if err != nil { - api.Backend.LogError("warehouse.CreateFile", "status %d error: %v", status, err) + api.Backend.LogError("warehouse.CreateFile", "Status %d error: %v", status, err) } EncodeJSON(api.Backend, w, r, WarehouseResult{Status: status, Hash: hash}) } /* -apiWarehouseCreateFilePath creates a file in the warehouse by copying it from an existing file. -Warning: An attacker could supply any local file using this function, put them into storage and read them! No input path verification or limitation is done. +apiWarehouseCreateFilePath creates a File in the warehouse by copying it from an existing File. +Warning: An attacker could supply any local File using this function, put them into storage and read them! No input path verification or limitation is done. In the future the API should be secured using a random API key and setting the CORS header prohibiting regular browsers to access the API. Request: GET /warehouse/create/path?path=[target path on disk] @@ -54,24 +54,24 @@ func (api *WebapiInstance) apiWarehouseCreateFilePath(w http.ResponseWriter, r * hash, status, err := api.Backend.UserWarehouse.CreateFileFromPath(filePath) if err != nil { - api.Backend.LogError("warehouse.CreateFile", "status %d error: %v", status, err) + api.Backend.LogError("warehouse.CreateFile", "Status %d error: %v", status, err) } EncodeJSON(api.Backend, w, r, WarehouseResult{Status: status, Hash: hash}) } /* -apiWarehouseReadFile reads a file in the warehouse. +apiWarehouseReadFile reads a File in the warehouse. -Request: GET /warehouse/read?hash=[hash] - Optional parameters &offset=[file offset]&limit=[read limit in bytes] -Response: 200 with the raw file data - 404 if file was not found - 500 in case of internal error opening the file +Request: GET /warehouse/read?Hash=[Hash] + Optional parameters &offset=[File offset]&limit=[read limit in bytes] +Response: 200 with the raw File data + 404 if File was not found + 500 in case of internal error opening the File */ func (api *WebapiInstance) apiWarehouseReadFile(w http.ResponseWriter, r *http.Request) { r.ParseForm() - hash, valid1 := DecodeBlake3Hash(r.Form.Get("hash")) + hash, valid1 := DecodeBlake3Hash(r.Form.Get("Hash")) if !valid1 { http.Error(w, "", http.StatusBadRequest) return @@ -90,23 +90,23 @@ func (api *WebapiInstance) apiWarehouseReadFile(w http.ResponseWriter, r *http.R w.WriteHeader(http.StatusInternalServerError) return // Cannot catch warehouse.StatusErrorReadFile since data may have been already returned. - // In the future a special header indicating the expected file length could be sent (would require a callback in ReadFile), although the caller should already know the file size based on metadata. + // In the future a special header indicating the expected File length could be sent (would require a callback in ReadFile), although the caller should already know the File size based on metadata. } if err != nil { - api.Backend.LogError("warehouse.ReadFile", "status %d read %d error: %v", status, bytesRead, err) + api.Backend.LogError("warehouse.ReadFile", "Status %d read %d error: %v", status, bytesRead, err) } } /* -apiWarehouseDeleteFile deletes a file in the warehouse. +apiWarehouseDeleteFile deletes a File in the warehouse. -Request: GET /warehouse/delete?hash=[hash] +Request: GET /warehouse/delete?Hash=[Hash] Response: 200 with JSON structure WarehouseResult */ func (api *WebapiInstance) apiWarehouseDeleteFile(w http.ResponseWriter, r *http.Request) { r.ParseForm() - hash, valid1 := DecodeBlake3Hash(r.Form.Get("hash")) + hash, valid1 := DecodeBlake3Hash(r.Form.Get("Hash")) if !valid1 { http.Error(w, "", http.StatusBadRequest) return @@ -115,23 +115,23 @@ func (api *WebapiInstance) apiWarehouseDeleteFile(w http.ResponseWriter, r *http status, err := api.Backend.UserWarehouse.DeleteFile(hash) if err != nil { - api.Backend.LogError("warehouse.DeleteFile", "status %d error: %v", status, err) + api.Backend.LogError("warehouse.DeleteFile", "Status %d error: %v", status, err) } EncodeJSON(api.Backend, w, r, WarehouseResult{Status: status, Hash: hash}) } /* -apiWarehouseReadFilePath reads a file from the warehouse and stores it to the target file. It fails with StatusErrorTargetExists if the target file already exists. -The path must include the full directory and file name. +apiWarehouseReadFilePath reads a File from the warehouse and stores it to the target File. It fails with StatusErrorTargetExists if the target File already exists. +The path must include the full directory and File name. -Request: GET /warehouse/read/path?hash=[hash]&path=[target path on disk] - Optional parameters &offset=[file offset]&limit=[read limit in bytes] +Request: GET /warehouse/read/path?Hash=[Hash]&path=[target path on disk] + Optional parameters &offset=[File offset]&limit=[read limit in bytes] Response: 200 with JSON structure WarehouseResult */ func (api *WebapiInstance) apiWarehouseReadFilePath(w http.ResponseWriter, r *http.Request) { r.ParseForm() - hash, valid1 := DecodeBlake3Hash(r.Form.Get("hash")) + hash, valid1 := DecodeBlake3Hash(r.Form.Get("Hash")) if !valid1 { http.Error(w, "", http.StatusBadRequest) return @@ -144,7 +144,7 @@ func (api *WebapiInstance) apiWarehouseReadFilePath(w http.ResponseWriter, r *ht status, bytesRead, err := api.Backend.UserWarehouse.ReadFileToDisk(hash, int64(offset), int64(limit), targetFile) if err != nil { - api.Backend.LogError("warehouse.ReadFileToDisk", "status %d read %d error: %v", status, bytesRead, err) + api.Backend.LogError("warehouse.ReadFileToDisk", "Status %d read %d error: %v", status, bytesRead, err) } EncodeJSON(api.Backend, w, r, WarehouseResult{Status: status, Hash: hash})