github.com/dmmcquay/sia@v1.3.1-0.20180712220038-9f8d535311b9/cmd/siac/rentercmd.go (about) 1 package main 2 3 // TODO: If you run siac from a non-existant directory, the abs() function does 4 // not handle this very gracefully. 5 6 // TODO: Currently the download command will, every iteration, go through every 7 // single download in the download queue until it finds the right one. This 8 // doesn't end up hurting too much because it's very likely that the download 9 // you want is earlier instead of later in the queue, but it's still overhead 10 // that we should replace by using an API endpoint that allows you to ask for 11 // the desired download immediately instead of having to search through a list. 12 // 13 // The desired download should be specified by a unique ID instead of by a path, 14 // since a download to the same path can appear multiple times in the download 15 // history. This will need to be a new return value of the download call in the 16 // API. 17 18 // TODO: Currently the download command always displays speeds in terms of Mbps. 19 // This should probably be switched to some sort of human readable bandwidth 20 // display, so that it adjusts units as appropriate. 21 22 // TODO: Currently, the download command displays speed based on the total 23 // download time of the file, instead of using a rolling average over the last 24 // few minutes. We should change the download speed to use a rolling average. 25 26 import ( 27 "fmt" 28 "os" 29 "path/filepath" 30 "sort" 31 "strconv" 32 "strings" 33 "text/tabwriter" 34 "time" 35 36 "github.com/NebulousLabs/errors" 37 "github.com/spf13/cobra" 38 39 "github.com/NebulousLabs/Sia/modules" 40 "github.com/NebulousLabs/Sia/node/api" 41 "github.com/NebulousLabs/Sia/types" 42 ) 43 44 var ( 45 renterAllowanceCancelCmd = &cobra.Command{ 46 Use: "cancel", 47 Short: "Cancel the current allowance", 48 Long: "Cancel the current allowance, which controls how much money is spent on file contracts.", 49 Run: wrap(renterallowancecancelcmd), 50 } 51 52 renterAllowanceCmd = &cobra.Command{ 53 Use: "allowance", 54 Short: "View the current allowance", 55 Long: "View the current allowance, which controls how much money is spent on file contracts.", 56 Run: wrap(renterallowancecmd), 57 } 58 59 renterCmd = &cobra.Command{ 60 Use: "renter", 61 Short: "Perform renter actions", 62 Long: "Upload, download, rename, delete, load, or share files.", 63 Run: wrap(rentercmd), 64 } 65 66 renterContractsCmd = &cobra.Command{ 67 Use: "contracts", 68 Short: "View the Renter's contracts", 69 Long: "View the contracts that the Renter has formed with hosts.", 70 Run: wrap(rentercontractscmd), 71 } 72 73 renterContractsViewCmd = &cobra.Command{ 74 Use: "view [contract-id]", 75 Short: "View details of the specified contract", 76 Long: "View all details available of the specified contract.", 77 Run: wrap(rentercontractsviewcmd), 78 } 79 80 renterDownloadsCmd = &cobra.Command{ 81 Use: "downloads", 82 Short: "View the download queue", 83 Long: "View the list of files currently downloading.", 84 Run: wrap(renterdownloadscmd), 85 } 86 87 renterFilesDeleteCmd = &cobra.Command{ 88 Use: "delete [path]", 89 Aliases: []string{"rm"}, 90 Short: "Delete a file", 91 Long: "Delete a file. Does not delete the file on disk.", 92 Run: wrap(renterfilesdeletecmd), 93 } 94 95 renterFilesDownloadCmd = &cobra.Command{ 96 Use: "download [path] [destination]", 97 Short: "Download a file", 98 Long: "Download a previously-uploaded file to a specified destination.", 99 Run: wrap(renterfilesdownloadcmd), 100 } 101 102 renterFilesListCmd = &cobra.Command{ 103 Use: "list", 104 Aliases: []string{"ls"}, 105 Short: "List the status of all files", 106 Long: "List the status of all files known to the renter on the Sia network.", 107 Run: wrap(renterfileslistcmd), 108 } 109 110 renterFilesRenameCmd = &cobra.Command{ 111 Use: "rename [path] [newpath]", 112 Aliases: []string{"mv"}, 113 Short: "Rename a file", 114 Long: "Rename a file.", 115 Run: wrap(renterfilesrenamecmd), 116 } 117 118 renterFilesUploadCmd = &cobra.Command{ 119 Use: "upload [source] [path]", 120 Short: "Upload a file", 121 Long: "Upload a file to [path] on the Sia network.", 122 Run: wrap(renterfilesuploadcmd), 123 } 124 125 renterPricesCmd = &cobra.Command{ 126 Use: "prices", 127 Short: "Display the price of storage and bandwidth", 128 Long: "Display the estimated prices of storing files, retrieving files, and creating a set of contracts", 129 Run: wrap(renterpricescmd), 130 } 131 132 renterSetAllowanceCmd = &cobra.Command{ 133 Use: "setallowance [amount] [period] [hosts] [renew window]", 134 Short: "Set the allowance", 135 Long: `Set the amount of money that can be spent over a given period. 136 137 amount is given in currency units (SC, KS, etc.) 138 139 period is given in either blocks (b), hours (h), days (d), or weeks (w). A 140 block is approximately 10 minutes, so one hour is six blocks, a day is 144 141 blocks, and a week is 1008 blocks. 142 143 The Sia renter module spreads data across more than one Sia server computer 144 or "host". The "hosts" parameter for the setallowance command determines 145 how many different hosts the renter will spread the data across. 146 147 Allowance can be automatically renewed periodically. If the current 148 blockheight + the renew window >= the end height the contract, 149 then the contract is renewed automatically. 150 151 Note that setting the allowance will cause siad to immediately begin forming 152 contracts! You should only set the allowance once you are fully synced and you 153 have a reasonable number (>30) of hosts in your hostdb.`, 154 Run: rentersetallowancecmd, 155 } 156 157 renterUploadsCmd = &cobra.Command{ 158 Use: "uploads", 159 Short: "View the upload queue", 160 Long: "View the list of files currently uploading.", 161 Run: wrap(renteruploadscmd), 162 } 163 ) 164 165 // abs returns the absolute representation of a path. 166 // TODO: bad things can happen if you run siac from a non-existent directory. 167 // Implement some checks to catch this problem. 168 func abs(path string) string { 169 abspath, err := filepath.Abs(path) 170 if err != nil { 171 return path 172 } 173 return abspath 174 } 175 176 // rentercmd displays the renter's financial metrics and lists the files it is 177 // tracking. 178 func rentercmd() { 179 rg, err := httpClient.RenterGet() 180 if err != nil { 181 die("Could not get renter info:", err) 182 } 183 fm := rg.FinancialMetrics 184 totalSpent := fm.ContractFees.Add(fm.UploadSpending). 185 Add(fm.DownloadSpending).Add(fm.StorageSpending) 186 // Calculate unspent allocated 187 unspentAllocated := types.ZeroCurrency 188 if fm.TotalAllocated.Cmp(totalSpent) >= 0 { 189 unspentAllocated = fm.TotalAllocated.Sub(totalSpent) 190 } 191 // Calculate unspent unallocated 192 unspentUnallocated := types.ZeroCurrency 193 if fm.Unspent.Cmp(unspentAllocated) >= 0 { 194 unspentUnallocated = fm.Unspent.Sub(unspentAllocated) 195 } 196 197 fmt.Printf(`Renter info: 198 Allowance: %v 199 Period Spending: 200 Spent Funds: %v 201 Storage: %v 202 Upload: %v 203 Download: %v 204 Fees: %v 205 Unspent Funds: %v 206 Allocated: %v 207 Unallocated: %v 208 Previous Spending: 209 Withheld Funds: %v 210 Release Block: %v 211 Spent Funds: %v 212 213 `, currencyUnits(rg.Settings.Allowance.Funds), currencyUnits(totalSpent), 214 currencyUnits(fm.StorageSpending), currencyUnits(fm.UploadSpending), 215 currencyUnits(fm.DownloadSpending), currencyUnits(fm.ContractFees), 216 currencyUnits(fm.Unspent), currencyUnits(unspentAllocated), 217 currencyUnits(unspentUnallocated), currencyUnits(fm.WithheldFunds), 218 fm.ReleaseBlock, currencyUnits(fm.PreviousSpending)) 219 220 // also list files 221 renterfileslistcmd() 222 } 223 224 // renteruploadscmd is the handler for the command `siac renter uploads`. 225 // Lists files currently uploading. 226 func renteruploadscmd() { 227 rf, err := httpClient.RenterFilesGet() 228 if err != nil { 229 die("Could not get upload queue:", err) 230 } 231 232 // TODO: add a --history flag to the uploads command to mirror the --history 233 // flag in the downloads command. This hasn't been done yet because the 234 // call to /renter/files includes files that have been shared with you, 235 // not just files you've uploaded. 236 237 // Filter out files that have been uploaded. 238 var filteredFiles []modules.FileInfo 239 for _, fi := range rf.Files { 240 if !fi.Available { 241 filteredFiles = append(filteredFiles, fi) 242 } 243 } 244 if len(filteredFiles) == 0 { 245 fmt.Println("No files are uploading.") 246 return 247 } 248 fmt.Println("Uploading", len(filteredFiles), "files:") 249 for _, file := range filteredFiles { 250 fmt.Printf("%13s %s (uploading, %0.2f%%)\n", filesizeUnits(int64(file.Filesize)), file.SiaPath, file.UploadProgress) 251 } 252 } 253 254 // renterdownloadscmd is the handler for the command `siac renter downloads`. 255 // Lists files currently downloading, and optionally previously downloaded 256 // files if the -H or --history flag is specified. 257 func renterdownloadscmd() { 258 queue, err := httpClient.RenterDownloadsGet() 259 if err != nil { 260 die("Could not get download queue:", err) 261 } 262 // Filter out files that have been downloaded. 263 var downloading []api.DownloadInfo 264 for _, file := range queue.Downloads { 265 if file.Received != file.Filesize { 266 downloading = append(downloading, file) 267 } 268 } 269 if len(downloading) == 0 { 270 fmt.Println("No files are downloading.") 271 } else { 272 fmt.Println("Downloading", len(downloading), "files:") 273 for _, file := range downloading { 274 fmt.Printf("%s: %5.1f%% %s -> %s\n", file.StartTime.Format("Jan 02 03:04 PM"), 100*float64(file.Received)/float64(file.Filesize), file.SiaPath, file.Destination) 275 } 276 } 277 if !renterShowHistory { 278 return 279 } 280 fmt.Println() 281 // Filter out files that are downloading. 282 var downloaded []api.DownloadInfo 283 for _, file := range queue.Downloads { 284 if file.Received == file.Filesize { 285 downloaded = append(downloaded, file) 286 } 287 } 288 if len(downloaded) == 0 { 289 fmt.Println("No files downloaded.") 290 } else { 291 fmt.Println("Downloaded", len(downloaded), "files:") 292 for _, file := range downloaded { 293 fmt.Printf("%s: %s -> %s\n", file.StartTime.Format("Jan 02 03:04 PM"), file.SiaPath, file.Destination) 294 } 295 } 296 } 297 298 // renterallowancecmd displays the current allowance. 299 func renterallowancecmd() { 300 rg, err := httpClient.RenterGet() 301 if err != nil { 302 die("Could not get allowance:", err) 303 } 304 allowance := rg.Settings.Allowance 305 306 // convert to SC 307 fmt.Printf(`Allowance: 308 Amount: %v 309 Period: %v blocks 310 `, currencyUnits(allowance.Funds), allowance.Period) 311 } 312 313 // renterallowancecancelcmd cancels the current allowance. 314 func renterallowancecancelcmd() { 315 fmt.Println(`Canceling your allowance will disable uploading new files, 316 repairing existing files, and renewing existing files. All files will cease 317 to be accessible after a short period of time.`) 318 again: 319 fmt.Print("Do you want to continue? [y/n] ") 320 var resp string 321 fmt.Scanln(&resp) 322 switch strings.ToLower(resp) { 323 case "y", "yes": 324 // continue below 325 case "n", "no": 326 return 327 default: 328 goto again 329 } 330 err := httpClient.RenterCancelAllowance() 331 if err != nil { 332 die("error canceling allowance:", err) 333 } 334 fmt.Println("Allowance canceled.") 335 } 336 337 // rentersetallowancecmd allows the user to set the allowance. 338 // the first two parameters, amount and period, are required. 339 // the second two parameters are optional: 340 // hosts integer number of hosts 341 // renewperiod how many blocks between renewals 342 func rentersetallowancecmd(cmd *cobra.Command, args []string) { 343 if len(args) < 2 || len(args) > 4 { 344 cmd.UsageFunc()(cmd) 345 os.Exit(exitCodeUsage) 346 } 347 hastings, err := parseCurrency(args[0]) 348 if err != nil { 349 die("Could not parse amount:", err) 350 } 351 blocks, err := parsePeriod(args[1]) 352 if err != nil { 353 die("Could not parse period:", err) 354 } 355 allowance := modules.Allowance{} 356 _, err = fmt.Sscan(hastings, &allowance.Funds) 357 if err != nil { 358 die("Could not parse amount:", err) 359 } 360 361 _, err = fmt.Sscan(blocks, &allowance.Period) 362 if err != nil { 363 die("Could not parse period:", err) 364 } 365 if len(args) > 2 { 366 hosts, err := strconv.Atoi(args[2]) 367 if err != nil { 368 die("Could not parse host count") 369 } 370 allowance.Hosts = uint64(hosts) 371 } 372 if len(args) > 3 { 373 renewWindow, err := parsePeriod(args[3]) 374 if err != nil { 375 die("Could not parse renew window") 376 } 377 _, err = fmt.Sscan(renewWindow, &allowance.RenewWindow) 378 if err != nil { 379 die("Could not parse renew window:", err) 380 } 381 } 382 err = httpClient.RenterPostAllowance(allowance) 383 if err != nil { 384 die("Could not set allowance:", err) 385 } 386 fmt.Println("Allowance updated.") 387 } 388 389 // byValue sorts contracts by their value in siacoins, high to low. If two 390 // contracts have the same value, they are sorted by their host's address. 391 type byValue []api.RenterContract 392 393 func (s byValue) Len() int { return len(s) } 394 func (s byValue) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 395 func (s byValue) Less(i, j int) bool { 396 cmp := s[i].RenterFunds.Cmp(s[j].RenterFunds) 397 if cmp == 0 { 398 return s[i].NetAddress < s[j].NetAddress 399 } 400 return cmp > 0 401 } 402 403 // rentercontractscmd is the handler for the comand `siac renter contracts`. 404 // It lists the Renter's contracts. 405 func rentercontractscmd() { 406 rc, err := httpClient.RenterInactiveContractsGet() 407 if err != nil { 408 die("Could not get contracts:", err) 409 } 410 if len(rc.ActiveContracts) == 0 && len(rc.InactiveContracts) == 0 && !renterAllContracts { 411 fmt.Println("No contracts in the current period.") 412 return 413 } 414 415 if len(rc.ActiveContracts) != 0 && len(rc.InactiveContracts) != 0 { 416 // Display Active Contracts 417 fmt.Println("Contracts in the Current Period") 418 sort.Sort(byValue(rc.ActiveContracts)) 419 var activeTotalStored uint64 420 var activeTotalRemaining, activeTotalSpent, activeTotalFees types.Currency 421 for _, c := range rc.ActiveContracts { 422 activeTotalStored += c.Size 423 activeTotalRemaining = activeTotalRemaining.Add(c.RenterFunds) 424 activeTotalSpent = activeTotalSpent.Add(c.TotalCost.Sub(c.RenterFunds).Sub(c.Fees)) 425 activeTotalFees = activeTotalFees.Add(c.Fees) 426 } 427 fmt.Printf(` 428 Active Contract Summary: 429 Number of Contracts: %v 430 Total stored: %9s 431 Total Remaining: %v 432 Total Spent: %v 433 Total Fees: %v 434 435 `, len(rc.ActiveContracts), filesizeUnits(int64(activeTotalStored)), currencyUnits(activeTotalRemaining), currencyUnits(activeTotalSpent), currencyUnits(activeTotalFees)) 436 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 437 fmt.Fprintln(w, "Host\tRemaining Funds\tSpent Funds\tSpent Fees\tData\tEnd Height\tID\tGoodForUpload\tGoodForRenew") 438 for _, c := range rc.ActiveContracts { 439 address := c.NetAddress 440 if address == "" { 441 address = "Host Removed" 442 } 443 fmt.Fprintf(w, "%v\t%8s\t%8s\t%8s\t%v\t%v\t%v\t%v\t%v\n", 444 address, 445 currencyUnits(c.RenterFunds), 446 currencyUnits(c.TotalCost.Sub(c.RenterFunds).Sub(c.Fees)), 447 currencyUnits(c.Fees), 448 filesizeUnits(int64(c.Size)), 449 c.EndHeight, 450 c.ID, 451 c.GoodForUpload, 452 c.GoodForRenew) 453 } 454 w.Flush() 455 456 // Display Inactive Contracts 457 sort.Sort(byValue(rc.InactiveContracts)) 458 var inactiveTotalStored uint64 459 var inactiveTotalRemaining, inactiveTotalSpent, inactiveTotalFees types.Currency 460 for _, c := range rc.InactiveContracts { 461 inactiveTotalStored += c.Size 462 inactiveTotalRemaining = inactiveTotalRemaining.Add(c.RenterFunds) 463 inactiveTotalSpent = inactiveTotalSpent.Add(c.TotalCost.Sub(c.RenterFunds).Sub(c.Fees)) 464 inactiveTotalFees = inactiveTotalFees.Add(c.Fees) 465 } 466 fmt.Printf(` 467 Inactive Contract Summary: 468 Number of Contracts: %v 469 Total stored: %9s 470 Total Remaining: %v 471 Total Spent: %v 472 Total Fees: %v 473 474 `, len(rc.InactiveContracts), filesizeUnits(int64(inactiveTotalStored)), currencyUnits(inactiveTotalRemaining), currencyUnits(inactiveTotalSpent), currencyUnits(inactiveTotalFees)) 475 w = tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 476 fmt.Fprintln(w, "Host\tRemaining Funds\tSpent Funds\tSpent Fees\tData\tEnd Height\tID\tGoodForUpload\tGoodForRenew") 477 for _, c := range rc.InactiveContracts { 478 address := c.NetAddress 479 if address == "" { 480 address = "Host Removed" 481 } 482 fmt.Fprintf(w, "%v\t%8s\t%8s\t%8s\t%v\t%v\t%v\t%v\t%v\n", 483 address, 484 currencyUnits(c.RenterFunds), 485 currencyUnits(c.TotalCost.Sub(c.RenterFunds).Sub(c.Fees)), 486 currencyUnits(c.Fees), 487 filesizeUnits(int64(c.Size)), 488 c.EndHeight, 489 c.ID, 490 c.GoodForUpload, 491 c.GoodForRenew) 492 } 493 w.Flush() 494 } 495 496 if renterAllContracts { 497 rce, err := httpClient.RenterExpiredContractsGet() 498 if err != nil { 499 die("Could not get expired contracts:", err) 500 } 501 if len(rc.ActiveContracts) == 0 && len(rc.InactiveContracts) == 0 && len(rce.ExpiredContracts) == 0 { 502 fmt.Println("No contracts have been formed.") 503 return 504 } 505 if len(rce.ExpiredContracts) == 0 { 506 fmt.Println("No expired contracts") 507 return 508 } 509 sort.Sort(byValue(rce.ExpiredContracts)) 510 var expiredTotalStored uint64 511 var expiredTotalWithheld, expiredTotalSpent, expiredTotalFees types.Currency 512 for _, c := range rce.ExpiredContracts { 513 expiredTotalStored += c.Size 514 expiredTotalWithheld = expiredTotalWithheld.Add(c.RenterFunds) 515 expiredTotalSpent = expiredTotalSpent.Add(c.TotalCost.Sub(c.RenterFunds).Sub(c.Fees)) 516 expiredTotalFees = expiredTotalFees.Add(c.Fees) 517 } 518 fmt.Printf(` 519 Expired Contract Summary: 520 Number of Contracts: %v 521 Total stored: %9s 522 Total Remaining: %v 523 Total Spent: %v 524 Total Fees: %v 525 526 `, len(rce.ExpiredContracts), filesizeUnits(int64(expiredTotalStored)), currencyUnits(expiredTotalWithheld), currencyUnits(expiredTotalSpent), currencyUnits(expiredTotalFees)) 527 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 528 fmt.Fprintln(w, "Host\tWithheld Funds\tSpent Funds\tSpent Fees\tData\tEnd Height\tID\tGoodForUpload\tGoodForRenew") 529 for _, c := range rce.ExpiredContracts { 530 address := c.NetAddress 531 if address == "" { 532 address = "Host Removed" 533 } 534 fmt.Fprintf(w, "%v\t%8s\t%8s\t%8s\t%v\t%v\t%v\t%v\t%v\n", 535 address, 536 currencyUnits(c.RenterFunds), 537 currencyUnits(c.TotalCost.Sub(c.RenterFunds).Sub(c.Fees)), 538 currencyUnits(c.Fees), 539 filesizeUnits(int64(c.Size)), 540 c.EndHeight, 541 c.ID, 542 c.GoodForUpload, 543 c.GoodForRenew) 544 } 545 w.Flush() 546 } 547 } 548 549 // rentercontractsviewcmd is the handler for the command `siac renter contracts <id>`. 550 // It lists details of a specific contract. 551 func rentercontractsviewcmd(cid string) { 552 rc, err := httpClient.RenterInactiveContractsGet() 553 if err != nil { 554 die("Could not get contract details: ", err) 555 } 556 rce, err := httpClient.RenterExpiredContractsGet() 557 if err != nil { 558 die("Could not get expired contract details: ", err) 559 } 560 561 contracts := append(rc.ActiveContracts, rc.InactiveContracts...) 562 contracts = append(contracts, rce.ExpiredContracts...) 563 564 for _, rc := range contracts { 565 if rc.ID.String() == cid { 566 hostInfo, err := httpClient.HostDbHostsGet(rc.HostPublicKey) 567 if err != nil { 568 die("Could not fetch details of host: ", err) 569 } 570 fmt.Printf(` 571 Contract %v 572 Host: %v (Public Key: %v) 573 574 Start Height: %v 575 End Height: %v 576 577 Total cost: %v (Fees: %v) 578 Funds Allocated: %v 579 Upload Spending: %v 580 Storage Spending: %v 581 Download Spending: %v 582 Remaining Funds: %v 583 584 File Size: %v 585 `, rc.ID, rc.NetAddress, rc.HostPublicKey.String(), rc.StartHeight, rc.EndHeight, 586 currencyUnits(rc.TotalCost), 587 currencyUnits(rc.Fees), 588 currencyUnits(rc.TotalCost.Sub(rc.Fees)), 589 currencyUnits(rc.UploadSpending), 590 currencyUnits(rc.StorageSpending), 591 currencyUnits(rc.DownloadSpending), 592 currencyUnits(rc.RenterFunds), 593 filesizeUnits(int64(rc.Size))) 594 595 printScoreBreakdown(&hostInfo) 596 return 597 } 598 } 599 600 fmt.Println("Contract not found") 601 } 602 603 // renterfilesdeletecmd is the handler for the command `siac renter delete [path]`. 604 // Removes the specified path from the Sia network. 605 func renterfilesdeletecmd(path string) { 606 err := httpClient.RenterDeletePost(path) 607 if err != nil { 608 die("Could not delete file:", err) 609 } 610 fmt.Println("Deleted", path) 611 } 612 613 // renterfilesdownloadcmd is the handler for the comand `siac renter download [path] [destination]`. 614 // Downloads a path from the Sia network to the local specified destination. 615 func renterfilesdownloadcmd(path, destination string) { 616 destination = abs(destination) 617 618 // Queue the download. An error will be returned if the queueing failed, but 619 // the call will return before the download has completed. The call is made 620 // as an async call. 621 err := httpClient.RenterDownloadFullGet(path, destination, true) 622 if err != nil { 623 die("Download could not be started:", err) 624 } 625 626 // If the download is async, report success. 627 if renterDownloadAsync { 628 fmt.Printf("Queued Download '%s' to %s.\n", path, abs(destination)) 629 return 630 } 631 632 // If the download is blocking, display progress as the file downloads. 633 err = downloadprogress(path, destination) 634 if err != nil { 635 die("\nDownload could not be completed:", err) 636 } 637 fmt.Printf("\nDownloaded '%s' to %s.\n", path, abs(destination)) 638 } 639 640 // downloadprogress will display the progress of the provided download to the 641 // user, and return an error when the download is finished. 642 func downloadprogress(siapath, destination string) error { 643 start := time.Now() 644 for range time.Tick(OutputRefreshRate) { 645 // Get the list of downloads. 646 queue, err := httpClient.RenterDownloadsGet() 647 if err != nil { 648 continue // benign 649 } 650 651 // Search for the download in the list of downloads. 652 var d api.DownloadInfo 653 found := false 654 for _, d = range queue.Downloads { 655 if d.SiaPath == siapath && d.Destination == destination { 656 found = true 657 break 658 } 659 } 660 // If the download has not appeared in the queue yet, either continue or 661 // give up. 662 if !found { 663 if time.Since(start) > RenterDownloadTimeout { 664 return errors.New("Unable to find download in queue") 665 } 666 continue 667 } 668 669 // Check whether the file has completed or otherwise errored out. 670 if d.Error != "" { 671 return errors.New(d.Error) 672 } 673 if d.Completed { 674 return nil 675 } 676 677 // Update the progress for the user. 678 pct := 100 * float64(d.Received) / float64(d.Filesize) 679 elapsed := time.Since(d.StartTime) 680 elapsed -= elapsed % time.Second // round to nearest second 681 mbps := (float64(d.Received*8) / 1e6) / time.Since(d.StartTime).Seconds() 682 fmt.Printf("\rDownloading... %5.1f%% of %v, %v elapsed, %.2f Mbps ", pct, filesizeUnits(int64(d.Filesize)), elapsed, mbps) 683 } 684 685 // This code is unreachable, but the complier requires this to be here. 686 return errors.New("ERROR: download progress reached code that should not be reachable.") 687 } 688 689 // bySiaPath implements sort.Interface for [] modules.FileInfo based on the 690 // SiaPath field. 691 type bySiaPath []modules.FileInfo 692 693 func (s bySiaPath) Len() int { return len(s) } 694 func (s bySiaPath) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 695 func (s bySiaPath) Less(i, j int) bool { return s[i].SiaPath < s[j].SiaPath } 696 697 // renterfileslistcmd is the handler for the command `siac renter list`. 698 // Lists files known to the renter on the network. 699 func renterfileslistcmd() { 700 var rf api.RenterFiles 701 rf, err := httpClient.RenterFilesGet() 702 if err != nil { 703 die("Could not get file list:", err) 704 } 705 if len(rf.Files) == 0 { 706 fmt.Println("No files have been uploaded.") 707 return 708 } 709 fmt.Println("Tracking", len(rf.Files), "files:") 710 var totalStored uint64 711 for _, file := range rf.Files { 712 totalStored += file.Filesize 713 } 714 fmt.Printf("Total uploaded: %9s\n", filesizeUnits(int64(totalStored))) 715 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 716 if renterListVerbose { 717 fmt.Fprintln(w, "File size\tAvailable\tUploaded\tProgress\tRedundancy\tRenewing\tOn Disk\tRecoverable\tSia path") 718 } 719 sort.Sort(bySiaPath(rf.Files)) 720 for _, file := range rf.Files { 721 fmt.Fprintf(w, "%9s", filesizeUnits(int64(file.Filesize))) 722 if renterListVerbose { 723 availableStr := yesNo(file.Available) 724 renewingStr := yesNo(file.Renewing) 725 redundancyStr := fmt.Sprintf("%.2f", file.Redundancy) 726 if file.Redundancy == -1 { 727 redundancyStr = "-" 728 } 729 uploadProgressStr := fmt.Sprintf("%.2f%%", file.UploadProgress) 730 _, err := os.Stat(file.LocalPath) 731 onDiskStr := yesNo(!os.IsNotExist(err)) 732 recoverableStr := yesNo(!(file.Redundancy < 1)) 733 if file.UploadProgress == -1 { 734 uploadProgressStr = "-" 735 } 736 fmt.Fprintf(w, "\t%s\t%9s\t%8s\t%10s\t%s\t%s\t%s", availableStr, filesizeUnits(int64(file.UploadedBytes)), uploadProgressStr, redundancyStr, renewingStr, onDiskStr, recoverableStr) 737 } 738 fmt.Fprintf(w, "\t%s", file.SiaPath) 739 if !renterListVerbose && !file.Available { 740 fmt.Fprintf(w, " (uploading, %0.2f%%)", file.UploadProgress) 741 } 742 fmt.Fprintln(w, "") 743 } 744 w.Flush() 745 } 746 747 // renterfilesrenamecmd is the handler for the command `siac renter rename [path] [newpath]`. 748 // Renames a file on the Sia network. 749 func renterfilesrenamecmd(path, newpath string) { 750 err := httpClient.RenterRenamePost(path, newpath) 751 if err != nil { 752 die("Could not rename file:", err) 753 } 754 fmt.Printf("Renamed %s to %s\n", path, newpath) 755 } 756 757 // renterfilesuploadcmd is the handler for the command `siac renter upload 758 // [source] [path]`. Uploads the [source] file to [path] on the Sia network. 759 // If [source] is a directory, all files inside it will be uploaded and named 760 // relative to [path]. 761 func renterfilesuploadcmd(source, path string) { 762 stat, err := os.Stat(source) 763 if err != nil { 764 die("Could not stat file or folder:", err) 765 } 766 767 if stat.IsDir() { 768 // folder 769 var files []string 770 err := filepath.Walk(source, func(path string, info os.FileInfo, err error) error { 771 if err != nil { 772 fmt.Println("Warning: skipping file:", err) 773 return nil 774 } 775 if info.IsDir() { 776 return nil 777 } 778 files = append(files, path) 779 return nil 780 }) 781 if err != nil { 782 die("Could not read folder:", err) 783 } else if len(files) == 0 { 784 die("Nothing to upload.") 785 } 786 for _, file := range files { 787 fpath, _ := filepath.Rel(source, file) 788 fpath = filepath.Join(path, fpath) 789 fpath = filepath.ToSlash(fpath) 790 err = httpClient.RenterUploadDefaultPost(abs(file), fpath) 791 if err != nil { 792 die("Could not upload file:", err) 793 } 794 } 795 fmt.Printf("Uploaded %d files into '%s'.\n", len(files), path) 796 } else { 797 // single file 798 err = httpClient.RenterUploadDefaultPost(abs(source), path) 799 if err != nil { 800 die("Could not upload file:", err) 801 } 802 fmt.Printf("Uploaded '%s' as %s.\n", abs(source), path) 803 } 804 } 805 806 // renterpricescmd is the handler for the command `siac renter prices`, which 807 // displays the prices of various storage operations. 808 func renterpricescmd() { 809 rpg, err := httpClient.RenterPricesGet() 810 if err != nil { 811 die("Could not read the renter prices:", err) 812 } 813 814 fmt.Println("Renter Prices (estimated):") 815 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 816 fmt.Fprintln(w, "\tFees for Creating a Set of Contracts:\t", currencyUnits(rpg.FormContracts)) 817 fmt.Fprintln(w, "\tDownload 1 TB:\t", currencyUnits(rpg.DownloadTerabyte)) 818 fmt.Fprintln(w, "\tStore 1 TB for 1 Month:\t", currencyUnits(rpg.StorageTerabyteMonth)) 819 fmt.Fprintln(w, "\tUpload 1 TB:\t", currencyUnits(rpg.UploadTerabyte)) 820 w.Flush() 821 }