added abstracted functions for download

This commit is contained in:
2022-12-10 06:29:50 +00:00
parent 5b503df4a5
commit fc7f159936
16 changed files with 590 additions and 520 deletions

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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}
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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.

View File

@@ -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

View File

@@ -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)

View File

@@ -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()

View File

@@ -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

View File

@@ -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
}

View File

@@ -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})