Skip to content
This repository has been archived by the owner on Nov 2, 2018. It is now read-only.

Commit

Permalink
Merge pull request #3003 from MSevey/i1920-singleFileAPI
Browse files Browse the repository at this point in the history
I1920 single file api
  • Loading branch information
David Vorick committed May 10, 2018
2 parents f6c156f + 9360c8b commit 018fc64
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 29 deletions.
52 changes: 37 additions & 15 deletions doc/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -845,20 +845,21 @@ description of the byte encoding.
Renter
------

| Route | HTTP verb |
| ----------------------------------------------------------------------- | --------- |
| [/renter](#renter-get) | GET |
| [/renter](#renter-post) | POST |
| [/renter/contracts](#rentercontracts-get) | GET |
| [/renter/downloads](#renterdownloads-get) | GET |
| [/renter/prices](#renterprices-get) | GET |
| [/renter/files](#renterfiles-get) | GET |
| [/renter/delete/*___siapath___](#renterdeletesiapath-post) | POST |
| [/renter/download/*___siapath___](#renterdownloadsiapath-get) | GET |
| [/renter/downloadasync/*___siapath___](#renterdownloadasyncsiapath-get) | GET |
| [/renter/rename/*___siapath___](#renterrenamesiapath-post) | POST |
| [/renter/stream/*___siapath___](#renterstreamsiapath-get) | GET |
| [/renter/upload/*___siapath___](#renteruploadsiapath-post) | POST |
| Route | HTTP verb |
| --------------------------------------------------------------------------| --------- |
| [/renter](#renter-get) | GET |
| [/renter](#renter-post) | POST |
| [/renter/contracts](#rentercontracts-get) | GET |
| [/renter/downloads](#renterdownloads-get) | GET |
| [/renter/prices](#renterprices-get) | GET |
| [/renter/files](#renterfiles-get) | GET |
| [/renter/file/*___siapath___](#renterfile___siapath___-get) | GET |
| [/renter/delete/*___siapath___](#renterdeletesiapath-post) | POST |
| [/renter/download/*___siapath___](#renterdownloadsiapath-get) | GET |
| [/renter/downloadasync/*___siapath___](#renterdownloadasyncsiapath-get) | GET |
| [/renter/rename/*___siapath___](#renterrenamesiapath-post) | POST |
| [/renter/stream/*___siapath___](#renterstreamsiapath-get) | GET |
| [/renter/upload/*___siapath___](#renteruploadsiapath-post) | POST |

For examples and detailed descriptions of request and response parameters,
refer to [Renter.md](/doc/api/Renter.md).
Expand Down Expand Up @@ -989,11 +990,32 @@ lists the status of all files.
}
```

#### /renter/file/*__siapath__ [GET]

lists the status of specified file.

###### JSON Response [(with comments)](/doc/api/Renter.md#json-response-4)
```javascript
{
"file": {
"siapath": "foo/bar.txt",
"localpath": "/home/foo/bar.txt",
"filesize": 8192, // bytes
"available": true,
"renewing": true,
"redundancy": 5,
"bytesuploaded": 209715200, // total bytes uploaded
"uploadprogress": 100, // percent
"expiration": 60000
}
}
```

#### /renter/prices [GET]

lists the estimated prices of performing various storage and data operations.

###### JSON Response [(with comments)](/doc/api/Renter.md#json-response-4)
###### JSON Response [(with comments)](/doc/api/Renter.md#json-response-5)
```javascript
{
"downloadterabyte": "1234", // hastings
Expand Down
79 changes: 65 additions & 14 deletions doc/api/Renter.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,21 @@ allocated funds.
Index
-----

| Route | HTTP verb |
| ----------------------------------------------------------------------- | --------- |
| [/renter](#renter-get) | GET |
| [/renter](#renter-post) | POST |
| [/renter/contracts](#rentercontracts-get) | GET |
| [/renter/downloads](#renterdownloads-get) | GET |
| [/renter/files](#renterfiles-get) | GET |
| [/renter/prices](#renter-prices-get) | GET |
| [/renter/delete/___*siapath___](#renterdelete___siapath___-post) | POST |
| [/renter/download/___*siapath___](#renterdownload__siapath___-get) | GET |
| [/renter/downloadasync/___*siapath___](#renterdownloadasync__siapath___-get) | GET |
| [/renter/rename/___*siapath___](#renterrename___siapath___-post) | POST |
| [/renter/stream/___*siapath___](#renterstreamsiapath-get) | GET |
| [/renter/upload/___*siapath___](#renterupload___siapath___-post) | POST |
| Route | HTTP verb |
| ------------------------------------------------------------------------------- | --------- |
| [/renter](#renter-get) | GET |
| [/renter](#renter-post) | POST |
| [/renter/contracts](#rentercontracts-get) | GET |
| [/renter/downloads](#renterdownloads-get) | GET |
| [/renter/files](#renterfiles-get) | GET |
| [/renter/file/*___siapath___](#renterfile___siapath___-get) | GET |
| [/renter/prices](#renter-prices-get) | GET |
| [/renter/delete/___*siapath___](#renterdelete___siapath___-post) | POST |
| [/renter/download/___*siapath___](#renterdownload__siapath___-get) | GET |
| [/renter/downloadasync/___*siapath___](#renterdownloadasync__siapath___-get) | GET |
| [/renter/rename/___*siapath___](#renterrename___siapath___-post) | POST |
| [/renter/stream/___*siapath___](#renterstreamsiapath-get) | GET |
| [/renter/upload/___*siapath___](#renterupload___siapath___-post) | POST |

#### /renter [GET]

Expand Down Expand Up @@ -309,6 +310,56 @@ lists the status of all files.
}
```

#### /renter/file/*___siapath___ [GET]

lists the status of specified file.

###### JSON Response
```javascript
{
"file": {
// Path to the file in the renter on the network.
"siapath": "foo/bar.txt",

// Path to the local file on disk.
"localpath": "/home/foo/bar.txt",

// Size of the file in bytes.
"filesize": 8192, // bytes

// true if the file is available for download. Files may be available
// before they are completely uploaded.
"available": true,

// true if the file's contracts will be automatically renewed by the
// renter.
"renewing": true,

// Average redundancy of the file on the network. Redundancy is
// calculated by dividing the amount of data uploaded in the file's open
// contracts by the size of the file. Redundancy does not necessarily
// correspond to availability. Specifically, a redundancy >= 1 does not
// indicate the file is available as there could be a chunk of the file
// with 0 redundancy.
"redundancy": 5,

// Total number of bytes successfully uploaded via current file contracts.
// This number includes padding and rendundancy, so a file with a size of
// 8192 bytes might be padded to 40 MiB and, with a redundancy of 5,
// encoded to 200 MiB for upload.
"uploadedbytes": 209715200, // bytes

// Percentage of the file uploaded, including redundancy. Uploading has
// completed when uploadprogress is 100. Files may be available for
// download before upload progress is 100.
"uploadprogress": 100, // percent

// Block height at which the file ceases availability.
"expiration": 60000
}
}
```

#### /renter/prices [GET]

lists the estimated prices of performing various storage and data operations.
Expand Down
3 changes: 3 additions & 0 deletions modules/renter.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,9 @@ type Renter interface {
// DownloadHistory lists all the files that have been scheduled for download.
DownloadHistory() []DownloadInfo

// File returns information on specific file queried by user
File(siaPath string) (FileInfo, error)

// FileList returns information on all of the files stored by the renter.
FileList() []FileInfo

Expand Down
56 changes: 56 additions & 0 deletions modules/renter/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,62 @@ func (r *Renter) FileList() []modules.FileInfo {
return fileList
}

// File returns file from siaPath queried by user.
// Update based on FileList
func (r *Renter) File(siaPath string) (modules.FileInfo, error) {
var fileInfo modules.FileInfo

// Get the file and its contracs
contractIDs := make(map[types.FileContractID]struct{})
lockID := r.mu.RLock()
file, exists := r.files[siaPath]
if !exists {
return fileInfo, ErrUnknownPath
}
file.mu.RLock()
for cid := range file.contracts {
contractIDs[cid] = struct{}{}
}
file.mu.RUnlock()
r.mu.RUnlock(lockID)

// Build 2 maps that map every contract id to its offline and goodForRenew
// status.
goodForRenew := make(map[types.FileContractID]bool)
offline := make(map[types.FileContractID]bool)
for cid := range contractIDs {
resolvedID := r.hostContractor.ResolveID(cid)
cu, ok := r.hostContractor.ContractUtility(resolvedID)
goodForRenew[cid] = ok && cu.GoodForRenew
offline[cid] = r.hostContractor.IsOffline(resolvedID)
}

// Build the FileInfo
lockID = r.mu.RLock()
file.mu.RLock()
renewing := true
var localPath string
tf, exists := r.tracking[file.name]
if exists {
localPath = tf.RepairPath
}
fileInfo = modules.FileInfo{
SiaPath: file.name,
LocalPath: localPath,
Filesize: file.size,
Renewing: renewing,
Available: file.available(offline),
Redundancy: file.redundancy(offline, goodForRenew),
UploadedBytes: file.uploadedBytes(),
UploadProgress: file.uploadProgress(),
Expiration: file.expiration(),
}
file.mu.RUnlock()
r.mu.RUnlock(lockID)

return fileInfo, nil
}

// RenameFile takes an existing file and changes the nickname. The original
// file must exist, and there must not be any file that already has the
// replacement nickname.
Expand Down
7 changes: 7 additions & 0 deletions node/api/client/renter.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ func (c *Client) RenterDownloadHTTPResponseGet(siaPath string, offset, length ui
return
}

// RenterFileGet uses the /renter/file/:siapath endpoint to query a file.
func (c *Client) RenterFileGet(siaPath string) (rf api.RenterFile, err error) {
siaPath = strings.TrimPrefix(siaPath, "/")
err = c.get("/renter/file/"+siaPath, &rf)
return
}

// RenterFilesGet requests the /renter/files resource.
func (c *Client) RenterFilesGet() (rf api.RenterFiles, err error) {
err = c.get("/renter/files", &rf)
Expand Down
17 changes: 17 additions & 0 deletions node/api/renter.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ type (
Downloads []DownloadInfo `json:"downloads"`
}

// RenterFile lists the file queried.
RenterFile struct {
File modules.FileInfo `json:"file"`
}

// RenterFiles lists the files known to the renter.
RenterFiles struct {
Files []modules.FileInfo `json:"files"`
Expand Down Expand Up @@ -373,6 +378,18 @@ func (api *API) renterRenameHandler(w http.ResponseWriter, req *http.Request, ps
WriteSuccess(w)
}

// renterFileHandler handles the API call to return specific file.
func (api *API) renterFileHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
file, err := api.renter.File(strings.TrimPrefix(ps.ByName("siapath"), "/"))
if err != nil {
WriteError(w, Error{err.Error()}, http.StatusBadRequest)
return
}
WriteJSON(w, RenterFile{
File: file,
})
}

// renterFilesHandler handles the API call to list all of the files.
func (api *API) renterFilesHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
WriteJSON(w, RenterFiles{
Expand Down
1 change: 1 addition & 0 deletions node/api/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func (api *API) buildHTTPRoutes(requiredUserAgent string, requiredPassword strin
router.GET("/renter/contracts", api.renterContractsHandler)
router.GET("/renter/downloads", api.renterDownloadsHandler)
router.GET("/renter/files", api.renterFilesHandler)
router.GET("/renter/file/*siapath", api.renterFileHandler)
router.GET("/renter/prices", api.renterPricesHandler)

// TODO: re-enable these routes once the new .sia format has been
Expand Down
9 changes: 9 additions & 0 deletions siatest/renter.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,15 @@ func (tn *TestNode) DownloadInfo(lf *LocalFile, rf *RemoteFile) (*api.DownloadIn
return di, err
}

// File returns the file queried by the user
func (tn *TestNode) File(siaPath string) (modules.FileInfo, error) {
rf, err := tn.RenterFileGet(siaPath)
if err != nil {
return rf.File, err
}
return rf.File, err
}

// Files lists the files tracked by the renter
func (tn *TestNode) Files() ([]modules.FileInfo, error) {
rf, err := tn.RenterFilesGet()
Expand Down
32 changes: 32 additions & 0 deletions siatest/renter/renter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func TestRenter(t *testing.T) {
}{
{"TestRenterStreamingCache", testRenterStreamingCache},
{"TestUploadDownload", testUploadDownload},
{"TestSingleFileGet", testSingleFileGet},
{"TestDownloadMultipleLargeSectors", testDownloadMultipleLargeSectors},
{"TestRenterLocalRepair", testRenterLocalRepair},
{"TestRenterRemoteRepair", testRenterRemoteRepair},
Expand Down Expand Up @@ -102,6 +103,37 @@ func testUploadDownload(t *testing.T, tg *siatest.TestGroup) {
}
}

// testSingleFileGet is a subtest that uses an existing TestGroup to test if
// using the signle file API endpoint works
func testSingleFileGet(t *testing.T, tg *siatest.TestGroup) {
// Grab the first of the group's renters
renter := tg.Renters()[0]
// Upload file, creating a piece for each host in the group
dataPieces := uint64(1)
parityPieces := uint64(len(tg.Hosts())) - dataPieces
fileSize := 100 + siatest.Fuzz()
_, _, err := renter.UploadNewFileBlocking(fileSize, dataPieces, parityPieces)
if err != nil {
t.Fatal("Failed to upload a file for testing: ", err)
}

files, err := renter.Files()
if err != nil {
t.Fatal("Failed to get renter files: ", err)
}

var file modules.FileInfo
for _, f := range files {
file, err = renter.File(f.SiaPath)
if err != nil {
t.Fatal("Failed to request single file", err)
}
if file != f {
t.Fatal("Single file queries does not match file previously requested.")
}
}
}

// testDownloadMultipleLargeSectors downloads multiple large files (>5 Sectors)
// in parallel and makes sure that the downloads are blocking each other.
func testDownloadMultipleLargeSectors(t *testing.T, tg *siatest.TestGroup) {
Expand Down

0 comments on commit 018fc64

Please sign in to comment.