github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/node/api/renter.go (about) 1 package api 2 3 import ( 4 "fmt" 5 "net/http" 6 "net/url" 7 "path/filepath" 8 "reflect" 9 "strconv" 10 "strings" 11 "time" 12 13 "SiaPrime/build" 14 "SiaPrime/modules" 15 "SiaPrime/modules/renter" 16 "SiaPrime/types" 17 "gitlab.com/NebulousLabs/errors" 18 19 "github.com/julienschmidt/httprouter" 20 ) 21 22 var ( 23 // recommendedHosts is the number of hosts that the renter will form 24 // contracts with if the value is not specified explicitly in the call to 25 // SetSettings. 26 recommendedHosts = build.Select(build.Var{ 27 Standard: uint64(50), 28 Dev: uint64(2), 29 Testing: uint64(4), 30 }).(uint64) 31 32 // requiredHosts specifies the minimum number of hosts that must be set in 33 // the renter settings for the renter settings to be valid. This minimum is 34 // there to prevent users from shooting themselves in the foot. 35 requiredHosts = build.Select(build.Var{ 36 Standard: uint64(20), 37 Dev: uint64(1), 38 Testing: uint64(1), 39 }).(uint64) 40 41 // requiredParityPieces specifies the minimum number of parity pieces that 42 // must be used when uploading a file. This minimum exists to prevent users 43 // from shooting themselves in the foot. 44 requiredParityPieces = build.Select(build.Var{ 45 Standard: int(12), 46 Dev: int(0), 47 Testing: int(0), 48 }).(int) 49 50 // requiredRedundancy specifies the minimum redundancy that will be 51 // accepted by the renter when uploading a file. This minimum exists to 52 // prevent users from shooting themselves in the foot. 53 requiredRedundancy = build.Select(build.Var{ 54 Standard: float64(2), 55 Dev: float64(1), 56 Testing: float64(1), 57 }).(float64) 58 59 // requiredRenewWindow establishes the minimum allowed renew window for the 60 // renter settings. This minimum is here to prevent users from shooting 61 // themselves in the foot. 62 requiredRenewWindow = build.Select(build.Var{ 63 Standard: types.BlockHeight(288), 64 Dev: types.BlockHeight(1), 65 Testing: types.BlockHeight(1), 66 }).(types.BlockHeight) 67 ) 68 69 type ( 70 // RenterGET contains various renter metrics. 71 RenterGET struct { 72 Settings modules.RenterSettings `json:"settings"` 73 FinancialMetrics modules.ContractorSpending `json:"financialmetrics"` 74 CurrentPeriod types.BlockHeight `json:"currentperiod"` 75 } 76 77 // RenterContract represents a contract formed by the renter. 78 RenterContract struct { 79 // Amount of contract funds that have been spent on downloads. 80 DownloadSpending types.Currency `json:"downloadspending"` 81 // Block height that the file contract ends on. 82 EndHeight types.BlockHeight `json:"endheight"` 83 // Fees paid in order to form the file contract. 84 Fees types.Currency `json:"fees"` 85 // Public key of the host the contract was formed with. 86 HostPublicKey types.SiaPublicKey `json:"hostpublickey"` 87 // ID of the file contract. 88 ID types.FileContractID `json:"id"` 89 // A signed transaction containing the most recent contract revision. 90 LastTransaction types.Transaction `json:"lasttransaction"` 91 // Address of the host the file contract was formed with. 92 NetAddress modules.NetAddress `json:"netaddress"` 93 // Remaining funds left for the renter to spend on uploads & downloads. 94 RenterFunds types.Currency `json:"renterfunds"` 95 // Size of the file contract, which is typically equal to the number of 96 // bytes that have been uploaded to the host. 97 Size uint64 `json:"size"` 98 // Block height that the file contract began on. 99 StartHeight types.BlockHeight `json:"startheight"` 100 // Amount of contract funds that have been spent on storage. 101 StorageSpending types.Currency `json:"storagespending"` 102 // DEPRECATED: This is the exact same value as StorageSpending, but it has 103 // incorrect capitalization. This was fixed in 1.3.2, but this field is kept 104 // to preserve backwards compatibility on clients who depend on the 105 // incorrect capitalization. This field will be removed in the future, so 106 // clients should switch to the StorageSpending field (above) with the 107 // correct lowercase name. 108 StorageSpendingDeprecated types.Currency `json:"StorageSpending"` 109 // Total cost to the wallet of forming the file contract. 110 TotalCost types.Currency `json:"totalcost"` 111 // Amount of contract funds that have been spent on uploads. 112 UploadSpending types.Currency `json:"uploadspending"` 113 // Signals if contract is good for uploading data 114 GoodForUpload bool `json:"goodforupload"` 115 // Signals if contract is good for a renewal 116 GoodForRenew bool `json:"goodforrenew"` 117 } 118 119 // RenterContracts contains the renter's contracts. 120 RenterContracts struct { 121 Contracts []RenterContract `json:"contracts"` 122 ActiveContracts []RenterContract `json:"activecontracts"` 123 InactiveContracts []RenterContract `json:"inactivecontracts"` 124 ExpiredContracts []RenterContract `json:"expiredcontracts"` 125 } 126 127 // RenterDownloadQueue contains the renter's download queue. 128 RenterDownloadQueue struct { 129 Downloads []DownloadInfo `json:"downloads"` 130 } 131 132 // RenterFile lists the file queried. 133 RenterFile struct { 134 File modules.FileInfo `json:"file"` 135 } 136 137 // RenterFiles lists the files known to the renter. 138 RenterFiles struct { 139 Files []modules.FileInfo `json:"files"` 140 } 141 142 // RenterLoad lists files that were loaded into the renter. 143 RenterLoad struct { 144 FilesAdded []string `json:"filesadded"` 145 } 146 147 // RenterPricesGET lists the data that is returned when a GET call is made 148 // to /renter/prices. 149 RenterPricesGET struct { 150 modules.RenterPriceEstimation 151 modules.Allowance 152 } 153 154 // RenterShareASCII contains an ASCII-encoded .sia file. 155 RenterShareASCII struct { 156 ASCIIsia string `json:"asciisia"` 157 } 158 159 // DownloadInfo contains all client-facing information of a file. 160 DownloadInfo struct { 161 Destination string `json:"destination"` // The destination of the download. 162 DestinationType string `json:"destinationtype"` // Can be "file", "memory buffer", or "http stream". 163 Filesize uint64 `json:"filesize"` // DEPRECATED. Same as 'Length'. 164 Length uint64 `json:"length"` // The length requested for the download. 165 Offset uint64 `json:"offset"` // The offset within the siafile requested for the download. 166 SiaPath string `json:"siapath"` // The siapath of the file used for the download. 167 168 Completed bool `json:"completed"` // Whether or not the download has completed. 169 EndTime time.Time `json:"endtime"` // The time when the download fully completed. 170 Error string `json:"error"` // Will be the empty string unless there was an error. 171 Received uint64 `json:"received"` // Amount of data confirmed and decoded. 172 StartTime time.Time `json:"starttime"` // The time when the download was started. 173 StartTimeUnix int64 `json:"starttimeunix"` // The time when the download was started in unix format. 174 TotalDataTransferred uint64 `json:"totaldatatransferred"` // The total amount of data transferred, including negotiation, overdrive etc. 175 } 176 ) 177 178 // renterHandlerGET handles the API call to /renter. 179 func (api *API) renterHandlerGET(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 180 settings := api.renter.Settings() 181 periodStart := api.renter.CurrentPeriod() 182 WriteJSON(w, RenterGET{ 183 Settings: settings, 184 FinancialMetrics: api.renter.PeriodSpending(), 185 CurrentPeriod: periodStart, 186 }) 187 } 188 189 // renterHandlerPOST handles the API call to set the Renter's settings. 190 func (api *API) renterHandlerPOST(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 191 // Get the existing settings 192 settings := api.renter.Settings() 193 194 // Scan the allowance amount. (optional parameter) 195 if f := req.FormValue("funds"); f != "" { 196 funds, ok := scanAmount(f) 197 if !ok { 198 WriteError(w, Error{"unable to parse funds"}, http.StatusBadRequest) 199 return 200 } 201 settings.Allowance.Funds = funds 202 } 203 // Scan the number of hosts to use. (optional parameter) 204 if h := req.FormValue("hosts"); h != "" { 205 var hosts uint64 206 if _, err := fmt.Sscan(h, &hosts); err != nil { 207 WriteError(w, Error{"unable to parse hosts: " + err.Error()}, http.StatusBadRequest) 208 return 209 } else if hosts != 0 && hosts < requiredHosts { 210 WriteError(w, Error{fmt.Sprintf("insufficient number of hosts, need at least %v but have %v", recommendedHosts, hosts)}, http.StatusBadRequest) 211 } else { 212 settings.Allowance.Hosts = hosts 213 } 214 } else if settings.Allowance.Hosts == 0 { 215 // Sane defaults if host haven't been set before. 216 settings.Allowance.Hosts = recommendedHosts 217 } 218 // Scan the period. (optional parameter) 219 if p := req.FormValue("period"); p != "" { 220 var period types.BlockHeight 221 if _, err := fmt.Sscan(p, &period); err != nil { 222 WriteError(w, Error{"unable to parse period: " + err.Error()}, http.StatusBadRequest) 223 return 224 } 225 settings.Allowance.Period = types.BlockHeight(period) 226 } else if settings.Allowance.Period == 0 { 227 WriteError(w, Error{"period needs to be set if it hasn't been set before"}, http.StatusBadRequest) 228 return 229 } 230 // Scan the renew window. (optional parameter) 231 if rw := req.FormValue("renewwindow"); rw != "" { 232 var renewWindow types.BlockHeight 233 if _, err := fmt.Sscan(rw, &renewWindow); err != nil { 234 WriteError(w, Error{"unable to parse renewwindow: " + err.Error()}, http.StatusBadRequest) 235 return 236 } else if renewWindow != 0 && types.BlockHeight(renewWindow) < requiredRenewWindow { 237 WriteError(w, Error{fmt.Sprintf("renew window is too small, must be at least %v blocks but have %v blocks", requiredRenewWindow, renewWindow)}, http.StatusBadRequest) 238 return 239 } else { 240 settings.Allowance.RenewWindow = types.BlockHeight(renewWindow) 241 } 242 } else if settings.Allowance.RenewWindow == 0 { 243 // Sane defaults if renew window hasn't been set before. 244 settings.Allowance.RenewWindow = settings.Allowance.Period / 2 245 } 246 // Scan the download speed limit. (optional parameter) 247 if d := req.FormValue("maxdownloadspeed"); d != "" { 248 var downloadSpeed int64 249 if _, err := fmt.Sscan(d, &downloadSpeed); err != nil { 250 WriteError(w, Error{"unable to parse downloadspeed: " + err.Error()}, http.StatusBadRequest) 251 return 252 } 253 settings.MaxDownloadSpeed = downloadSpeed 254 } 255 // Scan the upload speed limit. (optional parameter) 256 if u := req.FormValue("maxuploadspeed"); u != "" { 257 var uploadSpeed int64 258 if _, err := fmt.Sscan(u, &uploadSpeed); err != nil { 259 WriteError(w, Error{"unable to parse uploadspeed: " + err.Error()}, http.StatusBadRequest) 260 return 261 } 262 settings.MaxUploadSpeed = uploadSpeed 263 } 264 // Scan the stream cache size. (optional parameter) 265 if dcs := req.FormValue("streamcachesize"); dcs != "" { 266 var streamCacheSize uint64 267 if _, err := fmt.Sscan(dcs, &streamCacheSize); err != nil { 268 WriteError(w, Error{"unable to parse streamcachesize: " + err.Error()}, http.StatusBadRequest) 269 return 270 } 271 settings.StreamCacheSize = streamCacheSize 272 } 273 // Set the settings in the renter. 274 err := api.renter.SetSettings(settings) 275 if err != nil { 276 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 277 return 278 } 279 WriteSuccess(w) 280 } 281 282 // renterContractCancelHandler handles the API call to cancel a specific Renter contract. 283 func (api *API) renterContractCancelHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 284 var fcid types.FileContractID 285 if err := fcid.LoadString(req.FormValue("id")); err != nil { 286 WriteError(w, Error{"unable to parse id:" + err.Error()}, http.StatusBadRequest) 287 return 288 } 289 err := api.renter.CancelContract(fcid) 290 if err != nil { 291 WriteError(w, Error{"unable to cancel contract:" + err.Error()}, http.StatusBadRequest) 292 return 293 } 294 WriteSuccess(w) 295 } 296 297 // renterContractsHandler handles the API call to request the Renter's 298 // contracts. 299 // 300 // Active contracts are contracts that the renter is actively using to store 301 // data and can upload, download, and renew 302 // 303 // Inactive contracts are contracts that are not currently being used by the 304 // renter because they are !goodForRenew, but have endheights that are in the 305 // future so could potentially become active again 306 // 307 // Expired contracts are contracts who's endheights are in the past 308 func (api *API) renterContractsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 309 // Parse flags 310 inactive, err := scanBool(req.FormValue("inactive")) 311 if err != nil { 312 WriteError(w, Error{"unable to parse inactive:" + err.Error()}, http.StatusBadRequest) 313 return 314 } 315 expired, err := scanBool(req.FormValue("expired")) 316 if err != nil { 317 WriteError(w, Error{"unable to parse expired:" + err.Error()}, http.StatusBadRequest) 318 return 319 } 320 321 // Get current block height for reference 322 blockHeight := api.cs.Height() 323 324 // Get active contracts 325 contracts := []RenterContract{} 326 activeContracts := []RenterContract{} 327 inactiveContracts := []RenterContract{} 328 expiredContracts := []RenterContract{} 329 for _, c := range api.renter.Contracts() { 330 var size uint64 331 if len(c.Transaction.FileContractRevisions) != 0 { 332 size = c.Transaction.FileContractRevisions[0].NewFileSize 333 } 334 335 // Fetch host address 336 var netAddress modules.NetAddress 337 hdbe, exists := api.renter.Host(c.HostPublicKey) 338 if exists { 339 netAddress = hdbe.NetAddress 340 } 341 342 // Fetch utilities for contract 343 var goodForUpload bool 344 var goodForRenew bool 345 if utility, ok := api.renter.ContractUtility(c.HostPublicKey); ok { 346 goodForUpload = utility.GoodForUpload 347 goodForRenew = utility.GoodForRenew 348 } 349 contract := RenterContract{ 350 DownloadSpending: c.DownloadSpending, 351 EndHeight: c.EndHeight, 352 Fees: c.TxnFee.Add(c.SiafundFee).Add(c.ContractFee), 353 GoodForUpload: goodForUpload, 354 GoodForRenew: goodForRenew, 355 HostPublicKey: c.HostPublicKey, 356 ID: c.ID, 357 LastTransaction: c.Transaction, 358 NetAddress: netAddress, 359 RenterFunds: c.RenterFunds, 360 Size: size, 361 StartHeight: c.StartHeight, 362 StorageSpending: c.StorageSpending, 363 StorageSpendingDeprecated: c.StorageSpending, 364 TotalCost: c.TotalCost, 365 UploadSpending: c.UploadSpending, 366 } 367 if goodForRenew { 368 activeContracts = append(activeContracts, contract) 369 } else if inactive && !goodForRenew { 370 inactiveContracts = append(inactiveContracts, contract) 371 } 372 contracts = append(contracts, contract) 373 } 374 375 // Get expired contracts 376 if expired || inactive { 377 for _, c := range api.renter.OldContracts() { 378 var size uint64 379 if len(c.Transaction.FileContractRevisions) != 0 { 380 size = c.Transaction.FileContractRevisions[0].NewFileSize 381 } 382 383 // Fetch host address 384 var netAddress modules.NetAddress 385 hdbe, exists := api.renter.Host(c.HostPublicKey) 386 if exists { 387 netAddress = hdbe.NetAddress 388 } 389 390 // Fetch utilities for contract 391 var goodForUpload bool 392 var goodForRenew bool 393 if utility, ok := api.renter.ContractUtility(c.HostPublicKey); ok { 394 goodForUpload = utility.GoodForUpload 395 goodForRenew = utility.GoodForRenew 396 } 397 398 contract := RenterContract{ 399 DownloadSpending: c.DownloadSpending, 400 EndHeight: c.EndHeight, 401 Fees: c.TxnFee.Add(c.SiafundFee).Add(c.ContractFee), 402 GoodForUpload: goodForUpload, 403 GoodForRenew: goodForRenew, 404 HostPublicKey: c.HostPublicKey, 405 ID: c.ID, 406 LastTransaction: c.Transaction, 407 NetAddress: netAddress, 408 RenterFunds: c.RenterFunds, 409 Size: size, 410 StartHeight: c.StartHeight, 411 StorageSpending: c.StorageSpending, 412 StorageSpendingDeprecated: c.StorageSpending, 413 TotalCost: c.TotalCost, 414 UploadSpending: c.UploadSpending, 415 } 416 if expired && c.EndHeight < blockHeight { 417 expiredContracts = append(expiredContracts, contract) 418 } else if inactive && c.EndHeight >= blockHeight { 419 inactiveContracts = append(inactiveContracts, contract) 420 } 421 } 422 } 423 424 WriteJSON(w, RenterContracts{ 425 Contracts: contracts, 426 ActiveContracts: activeContracts, 427 InactiveContracts: inactiveContracts, 428 ExpiredContracts: expiredContracts, 429 }) 430 } 431 432 // renterClearDownloadsHandler handles the API call to request to clear the download queue. 433 func (api *API) renterClearDownloadsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 434 var afterTime time.Time 435 beforeTime := types.EndOfTime 436 beforeStr, afterStr := req.FormValue("before"), req.FormValue("after") 437 if beforeStr != "" { 438 beforeInt, err := strconv.ParseInt(beforeStr, 10, 64) 439 if err != nil { 440 WriteError(w, Error{"parsing integer value for parameter `before` failed: " + err.Error()}, http.StatusBadRequest) 441 return 442 } 443 beforeTime = time.Unix(0, beforeInt) 444 } 445 if afterStr != "" { 446 afterInt, err := strconv.ParseInt(afterStr, 10, 64) 447 if err != nil { 448 WriteError(w, Error{"parsing integer value for parameter `after` failed: " + err.Error()}, http.StatusBadRequest) 449 return 450 } 451 afterTime = time.Unix(0, afterInt) 452 } 453 454 err := api.renter.ClearDownloadHistory(afterTime, beforeTime) 455 if err != nil { 456 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 457 return 458 } 459 WriteSuccess(w) 460 } 461 462 // renterDownloadsHandler handles the API call to request the download queue. 463 func (api *API) renterDownloadsHandler(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) { 464 var downloads []DownloadInfo 465 for _, di := range api.renter.DownloadHistory() { 466 downloads = append(downloads, DownloadInfo{ 467 Destination: di.Destination, 468 DestinationType: di.DestinationType, 469 Filesize: di.Length, 470 Length: di.Length, 471 Offset: di.Offset, 472 SiaPath: di.SiaPath, 473 474 Completed: di.Completed, 475 EndTime: di.EndTime, 476 Error: di.Error, 477 Received: di.Received, 478 StartTime: di.StartTime, 479 StartTimeUnix: di.StartTimeUnix, 480 TotalDataTransferred: di.TotalDataTransferred, 481 }) 482 } 483 WriteJSON(w, RenterDownloadQueue{ 484 Downloads: downloads, 485 }) 486 } 487 488 // renterLoadHandler handles the API call to load a '.sia' file. 489 func (api *API) renterLoadHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 490 source, err := url.QueryUnescape(req.FormValue("source")) 491 if err != nil { 492 WriteError(w, Error{"failed to unescape the source path"}, http.StatusBadRequest) 493 return 494 } 495 if !filepath.IsAbs(source) { 496 WriteError(w, Error{"source must be an absolute path"}, http.StatusBadRequest) 497 return 498 } 499 500 files, err := api.renter.LoadSharedFiles(source) 501 if err != nil { 502 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 503 return 504 } 505 506 WriteJSON(w, RenterLoad{FilesAdded: files}) 507 } 508 509 // renterLoadAsciiHandler handles the API call to load a '.sia' file 510 // in ASCII form. 511 func (api *API) renterLoadASCIIHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 512 files, err := api.renter.LoadSharedFilesASCII(req.FormValue("asciisia")) 513 if err != nil { 514 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 515 return 516 } 517 518 WriteJSON(w, RenterLoad{FilesAdded: files}) 519 } 520 521 // renterRenameHandler handles the API call to rename a file entry in the 522 // renter. 523 func (api *API) renterRenameHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 524 newSiaPath, err := url.QueryUnescape(req.FormValue("newsiapath")) 525 if err != nil { 526 WriteError(w, Error{"failed to unescape newsiapath"}, http.StatusBadRequest) 527 return 528 } 529 err = api.renter.RenameFile(strings.TrimPrefix(ps.ByName("siapath"), "/"), newSiaPath) 530 if err != nil { 531 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 532 return 533 } 534 WriteSuccess(w) 535 } 536 537 // renterFileHandler handles GET requests to the /renter/file/:siapath API endpoint. 538 func (api *API) renterFileHandlerGET(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 539 file, err := api.renter.File(strings.TrimPrefix(ps.ByName("siapath"), "/")) 540 if err != nil { 541 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 542 return 543 } 544 WriteJSON(w, RenterFile{ 545 File: file, 546 }) 547 } 548 549 // renterFileHandler handles POST requests to the /renter/file/:siapath API endpoint. 550 func (api *API) renterFileHandlerPOST(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 551 newTrackingPath := req.FormValue("trackingpath") 552 553 // Handle changing the tracking path of a file. 554 if newTrackingPath != "" { 555 siapath := strings.TrimPrefix(ps.ByName("siapath"), "/") 556 if err := api.renter.SetFileTrackingPath(siapath, newTrackingPath); err != nil { 557 WriteError(w, Error{"unable to parse funds"}, http.StatusBadRequest) 558 return 559 } 560 } 561 WriteSuccess(w) 562 } 563 564 // renterFilesHandler handles the API call to list all of the files. 565 func (api *API) renterFilesHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 566 WriteJSON(w, RenterFiles{ 567 Files: api.renter.FileList(), 568 }) 569 } 570 571 // renterPricesHandler reports the expected costs of various actions given the 572 // renter settings and the set of available hosts. 573 func (api *API) renterPricesHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 574 allowance := modules.Allowance{} 575 // Scan the allowance amount. (optional parameter) 576 if f := req.FormValue("funds"); f != "" { 577 funds, ok := scanAmount(f) 578 if !ok { 579 WriteError(w, Error{"unable to parse funds"}, http.StatusBadRequest) 580 return 581 } 582 allowance.Funds = funds 583 } 584 // Scan the number of hosts to use. (optional parameter) 585 if h := req.FormValue("hosts"); h != "" { 586 var hosts uint64 587 if _, err := fmt.Sscan(h, &hosts); err != nil { 588 WriteError(w, Error{"unable to parse hosts: " + err.Error()}, http.StatusBadRequest) 589 return 590 } else if hosts != 0 && hosts < requiredHosts { 591 WriteError(w, Error{fmt.Sprintf("insufficient number of hosts, need at least %v but have %v", recommendedHosts, hosts)}, http.StatusBadRequest) 592 } else { 593 allowance.Hosts = hosts 594 } 595 } 596 // Scan the period. (optional parameter) 597 if p := req.FormValue("period"); p != "" { 598 var period types.BlockHeight 599 if _, err := fmt.Sscan(p, &period); err != nil { 600 WriteError(w, Error{"unable to parse period: " + err.Error()}, http.StatusBadRequest) 601 return 602 } 603 allowance.Period = types.BlockHeight(period) 604 } 605 // Scan the renew window. (optional parameter) 606 if rw := req.FormValue("renewwindow"); rw != "" { 607 var renewWindow types.BlockHeight 608 if _, err := fmt.Sscan(rw, &renewWindow); err != nil { 609 WriteError(w, Error{"unable to parse renewwindow: " + err.Error()}, http.StatusBadRequest) 610 return 611 } else if renewWindow != 0 && types.BlockHeight(renewWindow) < requiredRenewWindow { 612 WriteError(w, Error{fmt.Sprintf("renew window is too small, must be at least %v blocks but have %v blocks", requiredRenewWindow, renewWindow)}, http.StatusBadRequest) 613 return 614 } else { 615 allowance.RenewWindow = types.BlockHeight(renewWindow) 616 } 617 } 618 619 // Check for partially set allowance, which can happen since hosts and renew 620 // window can be optional fields. Checking here instead of assigning values 621 // above so that an empty allowance can still be submitted 622 if !reflect.DeepEqual(allowance, modules.Allowance{}) { 623 if allowance.Funds.Cmp(types.ZeroCurrency) == 0 { 624 WriteError(w, Error{fmt.Sprint("Allowance not set correctly, `funds` parameter left empty")}, http.StatusBadRequest) 625 return 626 } 627 if allowance.Period == 0 { 628 WriteError(w, Error{fmt.Sprint("Allowance not set correctly, `period` parameter left empty")}, http.StatusBadRequest) 629 return 630 } 631 if allowance.Hosts == 0 { 632 WriteError(w, Error{fmt.Sprint("Allowance not set correctly, `hosts` parameter left empty")}, http.StatusBadRequest) 633 return 634 } 635 if allowance.RenewWindow == 0 { 636 WriteError(w, Error{fmt.Sprint("Allowance not set correctly, `renewwindow` parameter left empty")}, http.StatusBadRequest) 637 return 638 } 639 } 640 641 estimate, a, err := api.renter.PriceEstimation(allowance) 642 if err != nil { 643 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 644 return 645 } 646 WriteJSON(w, RenterPricesGET{ 647 RenterPriceEstimation: estimate, 648 Allowance: a, 649 }) 650 } 651 652 // renterDeleteHandler handles the API call to delete a file entry from the 653 // renter. 654 func (api *API) renterDeleteHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 655 err := api.renter.DeleteFile(strings.TrimPrefix(ps.ByName("siapath"), "/")) 656 if err != nil { 657 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 658 return 659 } 660 661 WriteSuccess(w) 662 } 663 664 // renterDownloadHandler handles the API call to download a file. 665 func (api *API) renterDownloadHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 666 params, err := parseDownloadParameters(w, req, ps) 667 if err != nil { 668 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 669 return 670 } 671 if params.Async { 672 err = api.renter.DownloadAsync(params) 673 } else { 674 err = api.renter.Download(params) 675 } 676 if err != nil { 677 WriteError(w, Error{"download failed: " + err.Error()}, http.StatusInternalServerError) 678 return 679 } 680 if params.Httpwriter == nil { 681 // `httpresp=true` causes writes to w before this line is run, automatically 682 // adding `200 Status OK` code to response. Calling this results in a 683 // multiple calls to WriteHeaders() errors. 684 WriteSuccess(w) 685 return 686 } 687 } 688 689 // renterDownloadAsyncHandler handles the API call to download a file asynchronously. 690 func (api *API) renterDownloadAsyncHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 691 req.ParseForm() 692 req.Form.Set("async", "true") 693 api.renterDownloadHandler(w, req, ps) 694 } 695 696 // parseDownloadParameters parses the download parameters passed to the 697 // /renter/download endpoint. Validation of these parameters is done by the 698 // renter. 699 func parseDownloadParameters(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (modules.RenterDownloadParameters, error) { 700 destination, err := url.QueryUnescape(req.FormValue("destination")) 701 if err != nil { 702 return modules.RenterDownloadParameters{}, errors.AddContext(err, "failed to unescape the destination") 703 } 704 705 // The offset and length in bytes. 706 offsetparam := req.FormValue("offset") 707 lengthparam := req.FormValue("length") 708 709 // Determines whether the response is written to response body. 710 httprespparam := req.FormValue("httpresp") 711 712 // Determines whether to return on completion of download or straight away. 713 // If httprespparam is present, this parameter is ignored. 714 asyncparam := req.FormValue("async") 715 716 // Parse the offset and length parameters. 717 var offset, length uint64 718 if len(offsetparam) > 0 { 719 _, err := fmt.Sscan(offsetparam, &offset) 720 if err != nil { 721 return modules.RenterDownloadParameters{}, errors.AddContext(err, "could not decode the offset as uint64") 722 } 723 } 724 if len(lengthparam) > 0 { 725 _, err := fmt.Sscan(lengthparam, &length) 726 if err != nil { 727 return modules.RenterDownloadParameters{}, errors.AddContext(err, "could not decode the offset as uint64") 728 } 729 } 730 731 // Parse the httpresp parameter. 732 httpresp, err := scanBool(httprespparam) 733 if err != nil { 734 return modules.RenterDownloadParameters{}, errors.AddContext(err, "httpresp parameter could not be parsed") 735 } 736 737 // Parse the async parameter. 738 async, err := scanBool(asyncparam) 739 if err != nil { 740 return modules.RenterDownloadParameters{}, errors.AddContext(err, "async parameter could not be parsed") 741 } 742 743 siapath := strings.TrimPrefix(ps.ByName("siapath"), "/") // Sia file name. 744 745 dp := modules.RenterDownloadParameters{ 746 Destination: destination, 747 Async: async, 748 Length: length, 749 Offset: offset, 750 SiaPath: siapath, 751 } 752 if httpresp { 753 dp.Httpwriter = w 754 } 755 756 return dp, nil 757 } 758 759 // renterShareHandler handles the API call to create a '.sia' file that 760 // shares a set of file. 761 func (api *API) renterShareHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 762 destination, err := url.QueryUnescape(req.FormValue("destination")) 763 if err != nil { 764 WriteError(w, Error{"failed to unescape the destination path"}, http.StatusBadRequest) 765 return 766 } 767 // Check that the destination path is absolute. 768 if !filepath.IsAbs(destination) { 769 WriteError(w, Error{"destination must be an absolute path"}, http.StatusBadRequest) 770 return 771 } 772 773 err = api.renter.ShareFiles(strings.Split(req.FormValue("siapaths"), ","), destination) 774 if err != nil { 775 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 776 return 777 } 778 779 WriteSuccess(w) 780 } 781 782 // renterShareAsciiHandler handles the API call to return a '.sia' file 783 // in ascii form. 784 func (api *API) renterShareASCIIHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 785 ascii, err := api.renter.ShareFilesASCII(strings.Split(req.FormValue("siapaths"), ",")) 786 if err != nil { 787 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 788 return 789 } 790 WriteJSON(w, RenterShareASCII{ 791 ASCIIsia: ascii, 792 }) 793 } 794 795 // renterStreamHandler handles downloads from the /renter/stream endpoint 796 func (api *API) renterStreamHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 797 siaPath := strings.TrimPrefix(ps.ByName("siapath"), "/") 798 fileName, streamer, err := api.renter.Streamer(siaPath) 799 if err != nil { 800 WriteError(w, Error{fmt.Sprintf("failed to create download streamer: %v", err)}, 801 http.StatusInternalServerError) 802 return 803 } 804 http.ServeContent(w, req, fileName, time.Time{}, streamer) 805 } 806 807 // renterUploadHandler handles the API call to upload a file. 808 func (api *API) renterUploadHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 809 source, err := url.QueryUnescape(req.FormValue("source")) 810 if err != nil { 811 WriteError(w, Error{"failed to unescape the source path"}, http.StatusBadRequest) 812 return 813 } 814 if !filepath.IsAbs(source) { 815 WriteError(w, Error{"source must be an absolute path"}, http.StatusBadRequest) 816 return 817 } 818 819 // Check whether the erasure coding parameters have been supplied. 820 var ec modules.ErasureCoder 821 if req.FormValue("datapieces") != "" || req.FormValue("paritypieces") != "" { 822 // Check that both values have been supplied. 823 if req.FormValue("datapieces") == "" || req.FormValue("paritypieces") == "" { 824 WriteError(w, Error{"must provide both the datapieces parameter and the paritypieces parameter if specifying erasure coding parameters"}, http.StatusBadRequest) 825 return 826 } 827 828 // Parse the erasure coding parameters. 829 var dataPieces, parityPieces int 830 _, err := fmt.Sscan(req.FormValue("datapieces"), &dataPieces) 831 if err != nil { 832 WriteError(w, Error{"unable to read parameter 'datapieces': " + err.Error()}, http.StatusBadRequest) 833 return 834 } 835 _, err = fmt.Sscan(req.FormValue("paritypieces"), &parityPieces) 836 if err != nil { 837 WriteError(w, Error{"unable to read parameter 'paritypieces': " + err.Error()}, http.StatusBadRequest) 838 return 839 } 840 841 // Verify that sane values for parityPieces and redundancy are being 842 // supplied. 843 if parityPieces < requiredParityPieces { 844 WriteError(w, Error{fmt.Sprintf("a minimum of %v parity pieces is required, but %v parity pieces requested", parityPieces, requiredParityPieces)}, http.StatusBadRequest) 845 return 846 } 847 redundancy := float64(dataPieces+parityPieces) / float64(dataPieces) 848 if float64(dataPieces+parityPieces)/float64(dataPieces) < requiredRedundancy { 849 WriteError(w, Error{fmt.Sprintf("a redundancy of %.2f is required, but redundancy of %.2f supplied", redundancy, requiredRedundancy)}, http.StatusBadRequest) 850 return 851 } 852 853 // Create the erasure coder. 854 ec, err = renter.NewRSCode(dataPieces, parityPieces) 855 if err != nil { 856 WriteError(w, Error{"unable to encode file using the provided parameters: " + err.Error()}, http.StatusBadRequest) 857 return 858 } 859 } 860 861 // Call the renter to upload the file. 862 err = api.renter.Upload(modules.FileUploadParams{ 863 Source: source, 864 SiaPath: strings.TrimPrefix(ps.ByName("siapath"), "/"), 865 ErasureCode: ec, 866 }) 867 if err != nil { 868 WriteError(w, Error{"upload failed: " + err.Error()}, http.StatusInternalServerError) 869 return 870 } 871 WriteSuccess(w) 872 }