gitlab.com/SkynetLabs/skyd@v1.6.9/cmd/skyc/rentercmd.go (about) 1 package main 2 3 // TODO: If you run siac from a non-existent directory, the abs() function does 4 // not handle this very gracefully. 5 6 import ( 7 "bufio" 8 "encoding/json" 9 "fmt" 10 "math" 11 "os" 12 "path/filepath" 13 "sort" 14 "strconv" 15 "strings" 16 "sync" 17 "sync/atomic" 18 "text/tabwriter" 19 "time" 20 21 "github.com/spf13/cobra" 22 23 "gitlab.com/NebulousLabs/errors" 24 "gitlab.com/SkynetLabs/skyd/build" 25 "gitlab.com/SkynetLabs/skyd/node/api" 26 "gitlab.com/SkynetLabs/skyd/node/api/client" 27 "gitlab.com/SkynetLabs/skyd/skymodules" 28 "gitlab.com/SkynetLabs/skyd/skymodules/renter/filesystem" 29 "go.sia.tech/siad/modules" 30 "go.sia.tech/siad/types" 31 ) 32 33 const ( 34 fileSizeUnits = "B, KB, MB, GB, TB, PB, EB, ZB, YB" 35 36 // truncateErrLength is the length at which an error string gets truncated 37 truncateErrLength = 24 38 39 // colourful strings for the console UI 40 pBarJobProcess = "\x1b[34;1mpinning \x1b[0m" // blue 41 pBarJobUpload = "\x1b[33;1muploading \x1b[0m" // yellow 42 pBarJobDone = "\x1b[32;1mpinned! \x1b[0m" // green 43 ) 44 45 var ( 46 renterAllowanceCancelCmd = &cobra.Command{ 47 Use: "cancel", 48 Short: "Cancel the current allowance", 49 Long: "Cancel the current allowance, which controls how much money is spent on file contracts.", 50 Run: wrap(renterallowancecancelcmd), 51 } 52 53 renterAllowanceCmd = &cobra.Command{ 54 Use: "allowance", 55 Short: "View the current allowance", 56 Long: "View the current allowance, which controls how much money is spent on file contracts.", 57 Run: wrap(renterallowancecmd), 58 } 59 60 renterBubbleCmd = &cobra.Command{ 61 Use: "bubble [directory]", 62 Short: "Call bubble on a directory.", 63 Long: `Call bubble on a directory to manually force an update of the directories metadata. 64 To bubble the root directory pass in '.' as the directory.`, 65 Run: wrap(renterbubblecmd), 66 } 67 68 renterBackupCreateCmd = &cobra.Command{ 69 Use: "createbackup [name]", 70 Short: "Create a backup of the renter's siafiles", 71 Long: "Create a backup of the renter's siafiles, using the specified name.", 72 Run: wrap(renterbackupcreatecmd), 73 } 74 75 renterBackupLoadCmd = &cobra.Command{ 76 Use: "restorebackup [name]", 77 Short: "Restore a backup of the renter's siafiles", 78 Long: "Restore the backup of the renter's siafiles with the given name.", 79 Run: wrap(renterbackuprestorecmd), 80 } 81 82 renterBackupListCmd = &cobra.Command{ 83 Use: "listbackups", 84 Short: "List backups stored on hosts", 85 Long: "List backups stored on hosts", 86 Run: wrap(renterbackuplistcmd), 87 } 88 89 renterCleanCmd = &cobra.Command{ 90 Use: "clean", 91 Short: "Cleans up lost files", 92 Long: `WARNING: This action will permanently delete any files associated with 93 the renter that do not have a local copy on disk and have a redundancy of < 1. 94 95 You will be asked if you want to see these lost files. Additionally you can use 96 the command 'skyc renter lost' to see the renter's lost files.`, 97 Run: wrap(rentercleancmd), 98 } 99 100 renterCmd = &cobra.Command{ 101 Use: "renter", 102 Short: "Perform renter actions", 103 Long: "Upload, download, rename, delete, load, or share files.", 104 Run: wrap(rentercmd), 105 } 106 107 renterContractsCmd = &cobra.Command{ 108 Use: "contracts", 109 Short: "View the Renter's contracts", 110 Long: "View the contracts that the Renter has formed with hosts.", 111 Run: wrap(rentercontractscmd), 112 } 113 114 renterContractsRecoveryScanProgressCmd = &cobra.Command{ 115 Use: "recoveryscanprogress", 116 Short: "Returns the recovery scan progress.", 117 Long: "Returns the progress of a potentially ongoing recovery scan.", 118 Run: wrap(rentercontractrecoveryscanprogresscmd), 119 } 120 121 renterContractsViewCmd = &cobra.Command{ 122 Use: "view [contract-id]", 123 Short: "View details of the specified contract", 124 Long: "View all details available of the specified contract.", 125 Run: wrap(rentercontractsviewcmd), 126 } 127 128 renterDownloadsCmd = &cobra.Command{ 129 Use: "downloads", 130 Short: "View the download queue", 131 Long: "View the list of files currently downloading.", 132 Run: wrap(renterdownloadscmd), 133 } 134 135 renterDownloadCancelCmd = &cobra.Command{ 136 Use: "canceldownload [cancelID]", 137 Short: "Cancel async download", 138 Long: "Cancels an ongoing async download.", 139 Run: wrap(renterdownloadcancelcmd), 140 } 141 142 renterFilesDeleteCmd = &cobra.Command{ 143 Use: "delete [path]", 144 Aliases: []string{"rm"}, 145 Short: "Delete a file or folder", 146 Long: "Delete a file or folder. Does not delete the file/folder on disk. Multiple files may be deleted with space separation.", 147 Run: renterfilesdeletecmd, 148 } 149 150 renterFilesDownloadCmd = &cobra.Command{ 151 Use: "download [path] [destination]", 152 Short: "Download a file or folder", 153 Long: "Download a previously-uploaded file or folder to a specified destination.", 154 Run: wrap(renterfilesdownloadcmd), 155 } 156 157 renterFilesListCmd = &cobra.Command{ 158 Use: "ls [path]", 159 Short: "List the status of a specific file or all files within specified dir", 160 Long: "List the status of a specific file or all files known to the renter within the specified folder on the Sia network. To query the root dir either '\"\"', '/' or '.' can be supplied", 161 Run: renterfileslistcmd, 162 } 163 164 renterFilesRenameCmd = &cobra.Command{ 165 Use: "rename [path] [newpath]", 166 Aliases: []string{"mv"}, 167 Short: "Rename a file", 168 Long: "Rename a file.", 169 Run: wrap(renterfilesrenamecmd), 170 } 171 172 renterFuseCmd = &cobra.Command{ 173 Use: "fuse", 174 Short: "Perform fuse actions.", 175 Long: "List the set of fuse directories that are mounted", 176 Run: wrap(renterfusecmd), 177 } 178 179 renterFuseMountCmd = &cobra.Command{ 180 Use: "mount [path] [siapath]", 181 Short: "Mount a Sia folder to your disk", 182 Long: `Mount a Sia folder to your disk. Applications will be able to see this folder 183 as though it is a normal part of your filesystem. Currently experimental, and 184 read-only. When Sia is ready to support read-write fuse mounting, siac will be 185 updated to mount in read-write mode as the default. If you must guarantee that 186 read-only mode is used, you must use the API.`, 187 Run: wrap(renterfusemountcmd), 188 } 189 190 renterFuseUnmountCmd = &cobra.Command{ 191 Use: "unmount [path]", 192 Short: "Unmount a Sia folder", 193 Long: `Unmount a Sia folder that has previously been mounted. Unmount by specifying the 194 local path where the Sia folder is mounted.`, 195 Run: wrap(renterfuseunmountcmd), 196 } 197 198 renterSetLocalPathCmd = &cobra.Command{ 199 Use: "setlocalpath [siapath] [newlocalpath]", 200 Short: "Changes the local path of the file", 201 Long: "Changes the local path of the file", 202 Run: wrap(rentersetlocalpathcmd), 203 } 204 205 renterFilesUnstuckCmd = &cobra.Command{ 206 Use: "unstuckall", 207 Short: "Set all files to unstuck", 208 Long: "Set the 'stuck' status of every chunk in every file uploaded to the renter to 'false'.", 209 Run: wrap(renterfilesunstuckcmd), 210 } 211 212 renterFilesUploadCmd = &cobra.Command{ 213 Use: "upload [source] [path]", 214 Short: "Upload a file or folder", 215 Long: `Upload a file or folder to [path] on the Sia network. The --data-pieces and --parity-pieces 216 flags can be used to set a custom redundancy for the file.`, 217 Run: wrap(renterfilesuploadcmd), 218 } 219 220 renterFilesUploadPauseCmd = &cobra.Command{ 221 Use: "pause [duration]", 222 Short: "Pause renter uploads for a duration", 223 Long: `Temporarily pause renter uploads for the duration specified. 224 Available durations include "s" for seconds, "m" for minutes, and "h" for hours. 225 For Example: 'skyc renter upload pause 3h' would pause uploads for 3 hours.`, 226 Run: wrap(renterfilesuploadpausecmd), 227 } 228 229 renterFilesUploadResumeCmd = &cobra.Command{ 230 Use: "resume", 231 Short: "Resume renter uploads", 232 Long: "Resume renter uploads that were previously paused.", 233 Run: wrap(renterfilesuploadresumecmd), 234 } 235 236 renterPricesCmd = &cobra.Command{ 237 Use: "prices [amount] [period] [hosts] [renew window]", 238 Short: "Display the price of storage and bandwidth", 239 Long: `Display the estimated prices of storing files, retrieving files, and creating a 240 set of contracts. 241 242 An allowance can be provided for a more accurate estimate, if no allowance is 243 provided the current set allowance will be used, and if no allowance is set an 244 allowance of 500SC, 12w period, 50 hosts, and 4w renew window will be used.`, 245 Run: renterpricescmd, 246 } 247 248 renterRatelimitCmd = &cobra.Command{ 249 Use: "ratelimit [maxdownloadspeed] [maxuploadspeed]", 250 Short: "Set maxdownloadspeed and maxuploadspeed", 251 Long: `Set the maxdownloadspeed and maxuploadspeed in 252 Bytes per second: B/s, KB/s, MB/s, GB/s, TB/s 253 or 254 Bits per second: Bps, Kbps, Mbps, Gbps, Tbps 255 Set them to 0 for no limit.`, 256 Run: wrap(renterratelimitcmd), 257 } 258 259 renterSetAllowanceCmd = &cobra.Command{ 260 Use: "setallowance", 261 Short: "Set the allowance", 262 Long: `Set the amount of money that can be spent over a given period. 263 264 If no flags are set you will be walked through the interactive allowance 265 setting. To update only certain fields, pass in those values with the 266 corresponding field flag, for example '--amount 500SC'. 267 268 Allowance can be automatically renewed periodically. If the current 269 blockheight + the renew window >= the end height the contract, then the contract 270 is renewed automatically. 271 272 Note that setting the allowance will cause siad to immediately begin forming 273 contracts! You should only set the allowance once you are fully synced and you 274 have a reasonable number (>30) of hosts in your hostdb.`, 275 Run: rentersetallowancecmd, 276 } 277 278 renterTriggerContractRecoveryScanCmd = &cobra.Command{ 279 Use: "triggerrecoveryscan", 280 Short: "Triggers a recovery scan.", 281 Long: "Triggers a scan of the whole blockchain to find recoverable contracts.", 282 Run: wrap(rentertriggercontractrecoveryrescancmd), 283 } 284 285 renterUploadsCmd = &cobra.Command{ 286 Use: "uploads", 287 Short: "View the upload queue", 288 Long: "View the list of files currently uploading.", 289 Run: wrap(renteruploadscmd), 290 } 291 292 renterWorkersCmd = &cobra.Command{ 293 Use: "workers", 294 Short: "View the Renter's workers", 295 Long: "View the status of the Renter's workers", 296 Run: wrap(renterworkerscmd), 297 } 298 299 renterWorkersAccountsCmd = &cobra.Command{ 300 Use: "ea", 301 Short: "View the workers' ephemeral account", 302 Long: "View detailed information of the workers' ephemeral account", 303 Run: wrap(renterworkerseacmd), 304 } 305 306 renterWorkersDownloadsCmd = &cobra.Command{ 307 Use: "dj", 308 Short: "View the workers' download jobs", 309 Long: "View detailed information of the workers' download jobs", 310 Run: wrap(renterworkersdownloadscmd), 311 } 312 313 renterWorkersRepairsCmd = &cobra.Command{ 314 Use: "repj", 315 Short: "View the workers' repair jobs", 316 Long: "View detailed information of the workers' repair jobs", 317 Run: wrap(renterworkersrepairscmd), 318 } 319 320 renterWorkersHasSectorJobSCmd = &cobra.Command{ 321 Use: "hsj", 322 Short: "View the workers' has sector jobs", 323 Long: "View detailed information of the workers' has sector jobs", 324 Run: wrap(renterworkershsjcmd), 325 } 326 327 renterWorkersMaintenanceCmd = &cobra.Command{ 328 Use: "main", 329 Short: "View the workers' maintenance status", 330 Long: "View detailed information of the workers' maintenance state", 331 Run: wrap(renterworkersmaintenancecmd), 332 } 333 334 renterWorkersPriceTableCmd = &cobra.Command{ 335 Use: "pt", 336 Short: "View the workers' price table status", 337 Long: "View detailed information of the workers' price table", 338 Run: wrap(renterworkersptcmd), 339 } 340 341 renterWorkersReadJobsCmd = &cobra.Command{ 342 Use: "rj", 343 Short: "View the workers' read jobs", 344 Long: "View detailed information of the workers' read jobs", 345 Run: wrap(renterworkersrjcmd), 346 } 347 348 renterWorkersUploadsCmd = &cobra.Command{ 349 Use: "uj", 350 Short: "View the workers' upload jobs", 351 Long: "View detailed information of the workers' upload jobs", 352 Run: wrap(renterworkersuploadscmd), 353 } 354 355 renterWorkersReadRegistryCmd = &cobra.Command{ 356 Use: "rrj", 357 Short: "View the workers' read registry jobs", 358 Long: "View detailed information of the workers' read registry jobs", 359 Run: wrap(renterworkersreadregistrycmd), 360 } 361 362 renterWorkersUpdateRegistryCmd = &cobra.Command{ 363 Use: "urj", 364 Short: "View the workers' update registry jobs", 365 Long: "View detailed information of the workers' update registry jobs", 366 Run: wrap(renterworkersupdateregistrycmd), 367 } 368 369 renterHealthSummaryCmd = &cobra.Command{ 370 Use: "health", 371 Short: "Display a health summary of uploaded files", 372 Long: "Display a health summary of uploaded files", 373 Run: wrap(renterhealthsummarycmd), 374 } 375 376 renterLostCmd = &cobra.Command{ 377 Use: "lost", 378 Short: "Display the renter's lost files", 379 Long: "Display the renter's lost files", 380 Run: wrap(renterlostcmd), 381 } 382 383 renterContractInfoCmd = &cobra.Command{ 384 Use: "contractinfo", 385 Short: "Display contract related info", 386 Long: "Display contract related info. Useful when periodically piped to a file to get some historic contract data", 387 Run: wrap(rentercontractinfocmd), 388 } 389 390 renterContractInfoScanCmd = &cobra.Command{ 391 Use: "scan [path]", 392 Short: "Scans a file created with siac renter contractinfo", 393 Long: "Scans a file created with siac renter contractinfo and displays some useful information", 394 Run: wrap(rentercontractinfoscancmd), 395 } 396 ) 397 398 // contractsInfo describes info about a set of contracts at a given point in 399 // time. 400 type contractsInfo struct { 401 Contracts []contractInfo `json:"contracts"` 402 RepairData uint64 `json:"repairdata"` 403 Time int64 `json:"time"` 404 } 405 406 // activeContractInfo contains a contract's information plus some additional 407 // information aggregated during analysis of a file generated with 408 // rentercontractutilscmd. 409 type activeContractInfo struct { 410 contractInfo 411 412 changed []int64 413 } 414 415 // contractInfo describes info about the state of a contract. 416 type contractInfo struct { 417 State string `json:"state"` // active, passive, disabled 418 HostKey string `json:"hostkey"` 419 HostVersion string `json:"hostversion"` 420 ContractID string `json:"contractid"` 421 GoodForRenew bool `json:"goodforrenew"` 422 GoodForRefresh bool `json:"goodforrefresh"` 423 GoodForUpload bool `json:"goodforupload"` 424 BadContract bool `json:"badcontract"` 425 HostFound bool `json:"hostfound"` 426 HostRemainingStorage uint64 `json:"hostremainingstorage"` 427 HostOnline bool `json:"hostonline"` 428 HostMalicious bool `json:"hostmalicious"` 429 HostFiltered bool `json:"hostfiltered"` 430 Size uint64 `json:"size"` 431 } 432 433 // rentercleancmd cleans any lost files from the renter. 434 func rentercleancmd() { 435 // Print initial warning 436 fmt.Println("WARNING: This command will delete lost files and cannot be undone!") 437 438 // Ask user if they want to see the lost files they are about to delete. 439 confirmed := askForConfirmation("Would you like see the lost files that will be deleted?") 440 if confirmed { 441 renterlostcmd() 442 } 443 444 // Confirm user wants to proceed 445 confirmed = askForConfirmation("Are you sure you want to continue and delete the lost files?") 446 if !confirmed { 447 return 448 } 449 450 // Clean up lost files 451 fmt.Println("Cleaning lost files...") 452 err := httpClient.RenterCleanPost() 453 if err != nil { 454 die("Unable to clean renter's lost files:", err) 455 } 456 fmt.Println("Successfully cleaned lost files!") 457 } 458 459 // rentercmd displays the renter's financial metrics and high level renter info 460 func rentercmd() { 461 // For UX formating 462 defer fmt.Println() 463 464 // Get Renter 465 rg, err := httpClient.RenterGet() 466 if errors.Contains(err, api.ErrAPICallNotRecognized) { 467 // Assume module is not loaded if status command is not recognized. 468 fmt.Printf("Renter:\n Status: %s\n\n", moduleNotReadyStatus) 469 return 470 } else if err != nil { 471 die("Could not get renter info:", err) 472 } 473 474 // Print Allowance info 475 rate, err := types.ParseExchangeRate(build.ExchangeRate()) 476 if err != nil { 477 fmt.Printf("Warning: ignoring exchange rate - %s\n", err) 478 } 479 480 fmt.Println() 481 fmt.Printf(`Allowance:`) 482 if rg.Settings.Allowance.Funds.IsZero() { 483 fmt.Printf(" 0 SC (No current allowance)\n") 484 } else { 485 fm := rg.FinancialMetrics 486 totalSpent := fm.Fees.Sum().Add(fm.UploadSpending). 487 Add(fm.DownloadSpending).Add(fm.StorageSpending).Add(fm.FundAccountSpending).Add(fm.MaintenanceSpending.Sum()) 488 fmt.Printf(` %v 489 Spent Funds: %v 490 Unspent Funds: %v 491 `, currencyUnitsWithExchangeRate(rg.Settings.Allowance.Funds, rate), 492 currencyUnitsWithExchangeRate(totalSpent, rate), 493 currencyUnitsWithExchangeRate(fm.Unspent, rate)) 494 } 495 496 // detailed allowance spending for current period 497 if verbose { 498 renterallowancespending(rg) 499 } 500 501 // File and Contract Data 502 fmt.Println() 503 err = renterFilesAndContractSummary(verbose) 504 if err != nil { 505 die(err) 506 } 507 508 if !verbose { 509 return 510 } 511 512 // Print out the memory information for the renter 513 ms := rg.MemoryStatus 514 ud := ms.UserDownload 515 uu := ms.UserUpload 516 reg := ms.Registry 517 sys := ms.System 518 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 519 fmt.Fprintf(w, "\nMemory Status\tUser Download\tUser Upload\tRegistry\tSystem\tTotal\n") 520 fmt.Fprintf(w, " Available Memory\t%v\t%v\t%v\t%v\t%v\n", sizeString(ud.Available), sizeString(uu.Available), sizeString(reg.Available), sizeString(sys.Available), sizeString(ms.Available)) 521 fmt.Fprintf(w, " Starting Memory\t%v\t%v\t%v\t%v\t%v\n", sizeString(ud.Base), sizeString(uu.Base), sizeString(reg.Base), sizeString(sys.Base), sizeString(ms.Base)) 522 fmt.Fprintf(w, " Requested Memory\t%v\t%v\t%v\t%v\t%v\n", sizeString(ud.Requested), sizeString(uu.Requested), sizeString(reg.Requested), sizeString(sys.Requested), sizeString(ms.Requested)) 523 fmt.Fprintf(w, " \t \t \t \t \t \n") 524 fmt.Fprintf(w, " Available Priority Memory\t%v\t%v\t%v\t%v\t%v\n", sizeString(ud.PriorityAvailable), sizeString(uu.PriorityAvailable), sizeString(reg.PriorityAvailable), sizeString(sys.PriorityAvailable), sizeString(ms.PriorityAvailable)) 525 fmt.Fprintf(w, " Starting Priority Memory\t%v\t%v\t%v\t%v\t%v\n", sizeString(ud.PriorityBase), sizeString(uu.PriorityBase), sizeString(reg.PriorityBase), sizeString(sys.PriorityBase), sizeString(ms.PriorityBase)) 526 fmt.Fprintf(w, " Requested Priority Memory\t%v\t%v\t%v\t%v\t%v\n", sizeString(ud.PriorityRequested), sizeString(uu.PriorityRequested), sizeString(reg.PriorityRequested), sizeString(sys.PriorityRequested), sizeString(ms.PriorityRequested)) 527 fmt.Fprintln(w, "") 528 529 // Print out if the uploads are paused 530 if verbose { 531 var pauseEndTime time.Duration 532 if rg.Settings.UploadsStatus.PauseEndTime.After(time.Now()) { 533 pauseEndTime = time.Until(rg.Settings.UploadsStatus.PauseEndTime) 534 } 535 fmt.Fprintf(w, "\nUploads Status\n") 536 fmt.Fprintf(w, " Paused:\t%v\n", yesNo(rg.Settings.UploadsStatus.Paused)) 537 fmt.Fprintf(w, " Pause End Time:\t%v\n", pauseEndTime) 538 } 539 540 // Flush the writer 541 err = w.Flush() 542 if err != nil { 543 die(err) 544 } 545 546 // Print out ratelimit info about the renter 547 fmt.Println() 548 rateLimitSummary(rg.Settings.MaxDownloadSpeed, rg.Settings.MaxUploadSpeed) 549 } 550 551 // renterlostcmd is the handler for displaying the renter's lost files. 552 func renterlostcmd() { 553 // Grab the root directory information 554 dirs := getDir(skymodules.RootSiaPath(), true, false, verbose) 555 // Print the Aggregate Lost files 556 fmt.Println(dirs[0].dir.AggregateNumLostFiles, "lost files found.") 557 } 558 559 // renterhealthsummarycmd is the handler for displaying the overall health 560 // summary for uploaded files. 561 func renterhealthsummarycmd() { 562 // Print out file health summary for the renter 563 dirs := getDir(skymodules.RootSiaPath(), true, true, verbose) 564 renterFileHealthSummary(dirs) 565 } 566 567 // renteruploadscmd is the handler for the command `skyc renter uploads`. 568 // Lists files currently uploading. 569 func renteruploadscmd() { 570 rf, err := httpClient.RenterFilesGet(false) 571 if err != nil { 572 die("Could not get upload queue:", err) 573 } 574 575 // TODO: add a --history flag to the uploads command to mirror the --history 576 // flag in the downloads command. This hasn't been done yet because the 577 // call to /renter/files includes files that have been shared with you, 578 // not just files you've uploaded. 579 580 // Filter out files that have been uploaded. 581 var filteredFiles []skymodules.FileInfo 582 for _, fi := range rf.Files { 583 if !fi.Available { 584 filteredFiles = append(filteredFiles, fi) 585 } 586 } 587 if len(filteredFiles) == 0 { 588 fmt.Println("No files are uploading.") 589 return 590 } 591 fmt.Println("Uploading", len(filteredFiles), "files:") 592 for _, file := range filteredFiles { 593 fmt.Printf("%13s %s (uploading, %0.2f%%)\n", modules.FilesizeUnits(file.Filesize), file.SiaPath, file.UploadProgress) 594 } 595 } 596 597 // renterdownloadscmd is the handler for the command `skyc renter downloads`. 598 // Lists files currently downloading, and optionally previously downloaded 599 // files if the -H or --history flag is specified. 600 func renterdownloadscmd() { 601 queue, err := httpClient.RenterDownloadsGet() 602 if err != nil { 603 die("Could not get download queue:", err) 604 } 605 // Filter out files that have been downloaded. 606 var downloading []api.DownloadInfo 607 for _, file := range queue.Downloads { 608 if !file.Completed { 609 downloading = append(downloading, file) 610 } 611 } 612 if len(downloading) == 0 { 613 fmt.Println("No files are downloading.") 614 } else { 615 fmt.Println("Downloading", len(downloading), "files:") 616 for _, file := range downloading { 617 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) 618 } 619 } 620 if !renterShowHistory { 621 return 622 } 623 fmt.Println() 624 // Filter out files that are downloading. 625 var downloaded []api.DownloadInfo 626 for _, file := range queue.Downloads { 627 if file.Completed { 628 downloaded = append(downloaded, file) 629 } 630 } 631 if len(downloaded) == 0 { 632 fmt.Println("No files downloaded.") 633 } else { 634 fmt.Println("Downloaded", len(downloaded), "files:") 635 for _, file := range downloaded { 636 fmt.Printf("%s: %s -> %s\n", file.StartTime.Format("Jan 02 03:04 PM"), file.SiaPath, file.Destination) 637 } 638 } 639 } 640 641 // renterallowancecmd is the handler for the command `skyc renter allowance`. 642 // displays the current allowance. 643 func renterallowancecmd() { 644 rg, err := httpClient.RenterGet() 645 if err != nil { 646 die("Could not get allowance:", err) 647 } 648 allowance := rg.Settings.Allowance 649 650 // Show allowance info 651 rate, err := types.ParseExchangeRate(build.ExchangeRate()) 652 if err != nil { 653 fmt.Printf("Warning: ignoring exchange rate - %s\n", err) 654 } 655 656 fmt.Printf(`Allowance: 657 Amount: %v 658 Period: %v blocks 659 Renew Window: %v blocks 660 Hosts: %v 661 662 Skynet Portal Per-Contract Budget: %v 663 664 Expectations for period: 665 Expected Storage: %v 666 Expected Upload: %v 667 Expected Download: %v 668 Expected Redundancy: %v 669 670 Price Protections: 671 MaxRPCPrice: %v per million requests 672 MaxContractPrice: %v 673 MaxDownloadBandwidthPrice: %v per TB 674 MaxSectorAccessPrice: %v per million accesses 675 MaxStoragePrice: %v per TB per Month 676 MaxUploadBandwidthPrice: %v per TB 677 `, currencyUnitsWithExchangeRate(allowance.Funds, rate), allowance.Period, allowance.RenewWindow, 678 allowance.Hosts, currencyUnitsWithExchangeRate(allowance.PaymentContractInitialFunding, rate), 679 modules.FilesizeUnits(allowance.ExpectedStorage), 680 modules.FilesizeUnits(allowance.ExpectedUpload*uint64(allowance.Period)), 681 modules.FilesizeUnits(allowance.ExpectedDownload*uint64(allowance.Period)), 682 allowance.ExpectedRedundancy, 683 currencyUnits(allowance.MaxRPCPrice.Mul64(1e6)), 684 currencyUnits(allowance.MaxContractPrice), 685 currencyUnits(allowance.MaxDownloadBandwidthPrice.Mul(modules.BytesPerTerabyte)), 686 currencyUnits(allowance.MaxSectorAccessPrice.Mul64(1e6)), 687 currencyUnits(allowance.MaxStoragePrice.Mul(modules.BlockBytesPerMonthTerabyte)), 688 currencyUnits(allowance.MaxUploadBandwidthPrice.Mul(modules.BytesPerTerabyte))) 689 690 // Show detailed current Period spending metrics 691 renterallowancespending(rg) 692 693 fm := rg.FinancialMetrics 694 695 fmt.Printf("\n Previous Spending:") 696 if fm.PreviousSpending.IsZero() && fm.WithheldFunds.IsZero() { 697 fmt.Printf("\n No previous spending.\n\n") 698 } else { 699 fmt.Printf(` %v 700 Withheld Funds: %v 701 Release Block: %v 702 703 `, currencyUnitsWithExchangeRate(fm.PreviousSpending, rate), 704 currencyUnitsWithExchangeRate(fm.WithheldFunds, rate), fm.ReleaseBlock) 705 } 706 } 707 708 // renterallowancecancelcmd is the handler for `skyc renter allowance cancel`. 709 // cancels the current allowance. 710 func renterallowancecancelcmd() { 711 fmt.Println(`Canceling your allowance will disable uploading new files, 712 repairing existing files, and renewing existing files. All files will cease 713 to be accessible after a short period of time.`) 714 confirmed := askForConfirmation("Do you want to continue?") 715 if !confirmed { 716 return 717 } 718 err := httpClient.RenterAllowanceCancelPost() 719 if err != nil { 720 die("error canceling allowance:", err) 721 } 722 fmt.Println("Allowance canceled.") 723 } 724 725 // rentersetallowancecmd is the handler for `skyc renter setallowance`. 726 // set the allowance or modify individual allowance fields. 727 func rentersetallowancecmd(_ *cobra.Command, _ []string) { 728 // Get the current period setting. 729 rg, err := httpClient.RenterGet() 730 if err != nil { 731 die("Could not get renter settings") 732 } 733 734 req := httpClient.RenterPostPartialAllowance() 735 changedFields := 0 736 period := rg.Settings.Allowance.Period 737 738 // parse funds 739 if allowanceFunds != "" { 740 hastings, err := types.ParseCurrency(allowanceFunds) 741 if err != nil { 742 die("Could not parse amount:", err) 743 } 744 var funds types.Currency 745 _, err = fmt.Sscan(hastings, &funds) 746 if err != nil { 747 die("Could not parse amount:", err) 748 } 749 req = req.WithFunds(funds) 750 changedFields++ 751 } 752 // parse period 753 if allowancePeriod != "" { 754 blocks, err := parsePeriod(allowancePeriod) 755 if err != nil { 756 die("Could not parse period:", err) 757 } 758 _, err = fmt.Sscan(blocks, &period) 759 if err != nil { 760 die("Could not parse period:", err) 761 } 762 req = req.WithPeriod(period) 763 changedFields++ 764 } 765 // parse hosts 766 if allowanceHosts != "" { 767 hosts, err := strconv.Atoi(allowanceHosts) 768 if err != nil { 769 die("Could not parse host count:", err) 770 } 771 req = req.WithHosts(uint64(hosts)) 772 changedFields++ 773 } 774 // parse renewWindow 775 if allowanceRenewWindow != "" { 776 rw, err := parsePeriod(allowanceRenewWindow) 777 if err != nil { 778 die("Could not parse renew window:", err) 779 } 780 var renewWindow types.BlockHeight 781 _, err = fmt.Sscan(rw, &renewWindow) 782 if err != nil { 783 die("Could not parse renew window:", err) 784 } 785 req = req.WithRenewWindow(renewWindow) 786 changedFields++ 787 } 788 // parse the payment contract initial funding 789 if allowancePaymentContractInitialFunding != "" { 790 priceStr, err := types.ParseCurrency(allowancePaymentContractInitialFunding) 791 if err != nil { 792 die("Could not parse payment contract initial funding:", err) 793 } 794 var price types.Currency 795 _, err = fmt.Sscan(priceStr, &price) 796 if err != nil { 797 die("could not read payment contract initial funding:", err) 798 } 799 req = req.WithPaymentContractInitialFunding(price) 800 changedFields++ 801 } 802 // parse expectedStorage 803 if allowanceExpectedStorage != "" { 804 es, err := parseFilesize(allowanceExpectedStorage) 805 if err != nil { 806 die("Could not parse expected storage") 807 } 808 var expectedStorage uint64 809 _, err = fmt.Sscan(es, &expectedStorage) 810 if err != nil { 811 die("Could not parse expected storage") 812 } 813 req = req.WithExpectedStorage(expectedStorage) 814 changedFields++ 815 } 816 // parse expectedUpload 817 if allowanceExpectedUpload != "" { 818 eu, err := parseFilesize(allowanceExpectedUpload) 819 if err != nil { 820 die("Could not parse expected upload") 821 } 822 var expectedUpload uint64 823 _, err = fmt.Sscan(eu, &expectedUpload) 824 if err != nil { 825 die("Could not parse expected upload") 826 } 827 req = req.WithExpectedUpload(expectedUpload / uint64(period)) 828 changedFields++ 829 } 830 // parse expectedDownload 831 if allowanceExpectedDownload != "" { 832 ed, err := parseFilesize(allowanceExpectedDownload) 833 if err != nil { 834 die("Could not parse expected download") 835 } 836 var expectedDownload uint64 837 _, err = fmt.Sscan(ed, &expectedDownload) 838 if err != nil { 839 die("Could not parse expected download") 840 } 841 req = req.WithExpectedDownload(expectedDownload / uint64(period)) 842 changedFields++ 843 } 844 // parse expectedRedundancy 845 if allowanceExpectedRedundancy != "" { 846 expectedRedundancy, err := strconv.ParseFloat(allowanceExpectedRedundancy, 64) 847 if err != nil { 848 die("Could not parse expected redundancy") 849 } 850 req = req.WithExpectedRedundancy(expectedRedundancy) 851 changedFields++ 852 } 853 // parse maxrpcprice 854 if allowanceMaxRPCPrice != "" { 855 priceStr, err := types.ParseCurrency(allowanceMaxRPCPrice) 856 if err != nil { 857 die("Could not parse max rpc price:", err) 858 } 859 var price types.Currency 860 _, err = fmt.Sscan(priceStr, &price) 861 if err != nil { 862 die("Could not read max rpc price:", err) 863 } 864 price = price.Div64(1e6) 865 req = req.WithMaxRPCPrice(price) 866 changedFields++ 867 } 868 // parse maxcontractprice 869 if allowanceMaxContractPrice != "" { 870 priceStr, err := types.ParseCurrency(allowanceMaxContractPrice) 871 if err != nil { 872 die("Could not parse max contract price:", err) 873 } 874 var price types.Currency 875 _, err = fmt.Sscan(priceStr, &price) 876 if err != nil { 877 die("Could not read max contract price:", err) 878 } 879 req = req.WithMaxContractPrice(price) 880 changedFields++ 881 } 882 // parse maxdownloadbandwidthprice 883 if allowanceMaxDownloadBandwidthPrice != "" { 884 priceStr, err := types.ParseCurrency(allowanceMaxDownloadBandwidthPrice) 885 if err != nil { 886 die("Could not parse max download bandwidth price:", err) 887 } 888 var price types.Currency 889 _, err = fmt.Sscan(priceStr, &price) 890 if err != nil { 891 die("Could not read max download bandwidth price:", err) 892 } 893 price = price.Div(modules.BytesPerTerabyte) 894 req = req.WithMaxDownloadBandwidthPrice(price) 895 changedFields++ 896 } 897 // parse maxsectoraccessprice 898 if allowanceMaxSectorAccessPrice != "" { 899 priceStr, err := types.ParseCurrency(allowanceMaxSectorAccessPrice) 900 if err != nil { 901 die("Could not parse max sector access price:", err) 902 } 903 var price types.Currency 904 _, err = fmt.Sscan(priceStr, &price) 905 if err != nil { 906 die("Could not read max sector access price:", err) 907 } 908 price = price.Div64(1e6) 909 req = req.WithMaxSectorAccessPrice(price) 910 changedFields++ 911 } 912 // parse maxstorageprice 913 if allowanceMaxStoragePrice != "" { 914 priceStr, err := types.ParseCurrency(allowanceMaxStoragePrice) 915 if err != nil { 916 die("Could not parse max storage price:", err) 917 } 918 var price types.Currency 919 _, err = fmt.Sscan(priceStr, &price) 920 if err != nil { 921 die("Could not read max storage price:", err) 922 } 923 price = price.Div(modules.BlockBytesPerMonthTerabyte) 924 req = req.WithMaxStoragePrice(price) 925 changedFields++ 926 } 927 // parse maxuploadbandwidthprice 928 if allowanceMaxUploadBandwidthPrice != "" { 929 priceStr, err := types.ParseCurrency(allowanceMaxUploadBandwidthPrice) 930 if err != nil { 931 die("Could not parse max upload bandwidth price:", err) 932 } 933 var price types.Currency 934 _, err = fmt.Sscan(priceStr, &price) 935 if err != nil { 936 die("Could not read max upload bandwidth price:", err) 937 } 938 price = price.Div(modules.BytesPerTerabyte) 939 req = req.WithMaxUploadBandwidthPrice(price) 940 changedFields++ 941 } 942 943 // check if any fields were updated. 944 if changedFields == 0 { 945 // If no fields were set then walk the user through the interactive 946 // allowance setting 947 req = rentersetallowancecmdInteractive(req, rg.Settings.Allowance) 948 if err := req.Send(); err != nil { 949 die("Could not set allowance:", err) 950 } 951 fmt.Println("Allowance updated") 952 return 953 } 954 // check for required initial fields 955 if rg.Settings.Allowance.Funds.IsZero() && allowanceFunds == "" { 956 die("Funds must be set in initial allowance") 957 } 958 if rg.Settings.Allowance.ExpectedStorage == 0 && allowanceExpectedStorage == "" { 959 die("Expected storage must be set in initial allowance") 960 } 961 962 if err := req.Send(); err != nil { 963 die("Could not set allowance:", err) 964 } 965 fmt.Printf("Allowance updated. %v setting(s) changed.\n", changedFields) 966 } 967 968 // rentersetallowancecmdInteractive is the interactive handler for `skyc renter 969 // setallowance`. 970 func rentersetallowancecmdInteractive(req *client.AllowanceRequestPost, allowance skymodules.Allowance) *client.AllowanceRequestPost { 971 br := bufio.NewReader(os.Stdin) 972 readString := func() string { 973 str, _ := br.ReadString('\n') 974 return strings.TrimSpace(str) 975 } 976 977 fmt.Println("Interactive tool for setting the 8 allowance options.") 978 979 // funds 980 fmt.Println() 981 fmt.Println(`1/8: Funds 982 Funds determines the number of siacoins that the renter will spend when forming 983 contracts with hosts. The renter will not allocate more than this amount of 984 siacoins into the set of contracts each billing period. If the renter spends all 985 of the funds but then needs to form new contracts, the renter will wait until 986 either until the user increase the allowance funds, or until a new billing 987 period is reached. If there are not enough funds to repair all files, then files 988 may be at risk of getting lost. 989 990 Once the allowance is set, the renter will begin forming contracts. This will 991 immediately spend a large portion of the allowance, while also leaving a large 992 portion for forming additional contracts throughout the billing period. Most of 993 the funds that are spent immediately are not actually spent, but instead locked 994 up into state channels. In the allowance reports, these funds will typically be 995 reported as 'unspent allocated'. The funds that have been set aside for forming 996 contracts later in the billing cycle will be reported as 'unspent unallocated'. 997 998 The command 'skyc renter allowance' can be used to see a breakdown of spending. 999 1000 The following units can be used to set the allowance: 1001 1002 H (10^24 H per siacoin) 1003 SC (1 siacoin per SC) 1004 KS (1000 siacoins per KS)`) 1005 fmt.Println() 1006 fmt.Println("Current value:", currencyUnits(allowance.Funds)) 1007 fmt.Println("Default value:", currencyUnits(skymodules.DefaultAllowance.Funds)) 1008 1009 var funds types.Currency 1010 if allowance.Funds.IsZero() { 1011 funds = skymodules.DefaultAllowance.Funds 1012 fmt.Println("Enter desired value below, or leave blank to use default value") 1013 } else { 1014 funds = allowance.Funds 1015 fmt.Println("Enter desired value below, or leave blank to use current value") 1016 } 1017 for { 1018 fmt.Print("Funds: ") 1019 allowanceFunds := readString() 1020 if allowanceFunds == "" { 1021 break 1022 } 1023 1024 hastings, err := types.ParseCurrency(allowanceFunds) 1025 if err != nil { 1026 fmt.Printf("Could not parse currency in '%v': %v\n", allowanceFunds, err) 1027 continue 1028 } 1029 _, err = fmt.Sscan(hastings, &funds) 1030 if err != nil { 1031 fmt.Printf("Could not parse currency in '%v': %v\n", allowanceFunds, err) 1032 continue 1033 } 1034 if funds.IsZero() { 1035 fmt.Println("Allowance funds cannot be 0") 1036 continue 1037 } 1038 break 1039 } 1040 req = req.WithFunds(funds) 1041 1042 // period 1043 fmt.Println() 1044 fmt.Println(`2/8: Period 1045 The period is equivalent to the billing cycle length. The renter will not spend 1046 more than the full balance of its funds every billing period. When the billing 1047 period is over, the contracts will be renewed and the spending will be reset. 1048 1049 The following units can be used to set the period: 1050 1051 b (blocks - 10 minutes) 1052 d (days - 144 blocks or 1440 minutes) 1053 w (weeks - 1008 blocks or 10080 minutes)`) 1054 fmt.Println() 1055 fmt.Println("Current value:", periodUnits(allowance.Period), "weeks") 1056 fmt.Println("Default value:", periodUnits(skymodules.DefaultAllowance.Period), "weeks") 1057 1058 var period types.BlockHeight 1059 if allowance.Period == 0 { 1060 period = skymodules.DefaultAllowance.Period 1061 fmt.Println("Enter desired value below, or leave blank to use default value") 1062 } else { 1063 period = allowance.Period 1064 fmt.Println("Enter desired value below, or leave blank to use current value") 1065 } 1066 for { 1067 fmt.Print("Period: ") 1068 allowancePeriod := readString() 1069 if allowancePeriod == "" { 1070 break 1071 } 1072 1073 blocks, err := parsePeriod(allowancePeriod) 1074 if err != nil { 1075 fmt.Printf("Could not parse period in '%v': %v\n", allowancePeriod, err) 1076 continue 1077 } 1078 _, err = fmt.Sscan(blocks, &period) 1079 if err != nil { 1080 fmt.Printf("Could not parse period in '%v': %v\n", allowancePeriod, err) 1081 continue 1082 } 1083 if period == 0 { 1084 fmt.Println("Period cannot be 0") 1085 continue 1086 } 1087 break 1088 } 1089 req = req.WithPeriod(period) 1090 1091 // hosts 1092 fmt.Println() 1093 fmt.Println(`3/8: Hosts 1094 Hosts sets the number of hosts that will be used to form the allowance. Sia 1095 gains most of its resiliancy from having a large number of hosts. More hosts 1096 will mean both more robustness and higher speeds when using the network, however 1097 will also result in more memory consumption and higher blockchain fees. It is 1098 recommended that the default number of hosts be treated as a minimum, and that 1099 double the default number of default hosts be treated as a maximum.`) 1100 fmt.Println() 1101 fmt.Println("Current value:", allowance.Hosts) 1102 fmt.Println("Default value:", skymodules.DefaultAllowance.Hosts) 1103 1104 var hosts uint64 1105 if allowance.Hosts == 0 { 1106 hosts = skymodules.DefaultAllowance.Hosts 1107 fmt.Println("Enter desired value below, or leave blank to use default value") 1108 } else { 1109 hosts = allowance.Hosts 1110 fmt.Println("Enter desired value below, or leave blank to use current value") 1111 } 1112 for { 1113 fmt.Print("Hosts: ") 1114 allowanceHosts := readString() 1115 if allowanceHosts == "" { 1116 break 1117 } 1118 1119 hostsInt, err := strconv.Atoi(allowanceHosts) 1120 if err != nil { 1121 fmt.Printf("Could not parse host count in '%v': %v\n", allowanceHosts, err) 1122 continue 1123 } 1124 hosts = uint64(hostsInt) 1125 if hosts == 0 { 1126 fmt.Println("Must have at least 1 host") 1127 continue 1128 } 1129 break 1130 } 1131 req = req.WithHosts(hosts) 1132 1133 // renewWindow 1134 fmt.Println() 1135 fmt.Println(`4/8: Renew Window 1136 The renew window is how long the user has to renew their contracts. At the end 1137 of the period, all of the contracts expire. The contracts need to be renewed 1138 before they expire, otherwise the user will lose all of their files. The renew 1139 window is the window of time at the end of the period during which the renter 1140 will renew the users contracts. For example, if the renew window is 1 week long, 1141 then during the final week of each period the user will renew their contracts. 1142 If the user is offline for that whole week, the user's data will be lost. 1143 1144 Each billing period begins at the beginning of the renew window for the previous 1145 period. For example, if the period is 12 weeks long and the renew window is 4 1146 weeks long, then the first billing period technically begins at -4 weeks, or 4 1147 weeks before the allowance is created. And the second billing period begins at 1148 week 8, or 8 weeks after the allowance is created. The third billing period will 1149 begin at week 20. 1150 1151 The following units can be used to set the renew window: 1152 1153 b (blocks - 10 minutes) 1154 d (days - 144 blocks or 1440 minutes) 1155 w (weeks - 1008 blocks or 10080 minutes)`) 1156 fmt.Println() 1157 fmt.Println("Current value:", periodUnits(allowance.RenewWindow), "weeks") 1158 fmt.Println("Default value:", periodUnits(skymodules.DefaultAllowance.RenewWindow), "weeks") 1159 1160 var renewWindow types.BlockHeight 1161 if allowance.RenewWindow == 0 { 1162 renewWindow = skymodules.DefaultAllowance.RenewWindow 1163 fmt.Println("Enter desired value below, or leave blank to use default value") 1164 } else { 1165 renewWindow = allowance.RenewWindow 1166 fmt.Println("Enter desired value below, or leave blank to use current value") 1167 } 1168 for { 1169 fmt.Print("Renew Window: ") 1170 allowanceRenewWindow := readString() 1171 if allowanceRenewWindow == "" { 1172 break 1173 } 1174 1175 rw, err := parsePeriod(allowanceRenewWindow) 1176 if err != nil { 1177 fmt.Printf("Could not parse renew window in '%v': %v\n", allowanceRenewWindow, err) 1178 continue 1179 } 1180 _, err = fmt.Sscan(rw, &renewWindow) 1181 if err != nil { 1182 fmt.Printf("Could not parse renew window in '%v': %v\n", allowanceRenewWindow, err) 1183 continue 1184 } 1185 if renewWindow == 0 { 1186 fmt.Println("Cannot set renew window to zero") 1187 continue 1188 } 1189 break 1190 } 1191 req = req.WithRenewWindow(renewWindow) 1192 1193 // expectedStorage 1194 fmt.Println() 1195 fmt.Println(`5/8: Expected Storage 1196 Expected storage is the amount of storage that the user expects to keep on the 1197 Sia network. This value is important to calibrate the spending habits of siad. 1198 Because Sia is decentralized, there is no easy way for siad to know what the 1199 real world cost of storage is, nor what the real world price of a siacoin is. To 1200 overcome this deficiency, siad depends on the user for guidance. 1201 1202 If the user has a low allowance and a high amount of expected storage, siad will 1203 more heavily prioritize cheaper hosts, and will also be more comfortable with 1204 hosts that post lower amounts of collateral. If the user has a high allowance 1205 and a low amount of expected storage, siad will prioritize hosts that post more 1206 collateral, as well as giving preference to hosts better overall traits such as 1207 uptime and age. 1208 1209 Even when the user has a large allowance and a low amount of expected storage, 1210 siad will try to optimize for saving money; siad tries to meet the users storage 1211 and bandwidth needs while spending significantly less than the overall allowance. 1212 1213 The following units can be used to set the expected storage:`) 1214 fmt.Println() 1215 fmt.Printf(" %v\n", fileSizeUnits) 1216 fmt.Println() 1217 fmt.Println("Current value:", modules.FilesizeUnits(allowance.ExpectedStorage)) 1218 fmt.Println("Default value:", modules.FilesizeUnits(skymodules.DefaultAllowance.ExpectedStorage)) 1219 1220 var expectedStorage uint64 1221 if allowance.ExpectedStorage == 0 { 1222 expectedStorage = skymodules.DefaultAllowance.ExpectedStorage 1223 fmt.Println("Enter desired value below, or leave blank to use default value") 1224 } else { 1225 expectedStorage = allowance.ExpectedStorage 1226 fmt.Println("Enter desired value below, or leave blank to use current value") 1227 } 1228 for { 1229 fmt.Print("Expected Storage: ") 1230 allowanceExpectedStorage := readString() 1231 if allowanceExpectedStorage == "" { 1232 break 1233 } 1234 1235 es, err := parseFilesize(allowanceExpectedStorage) 1236 if err != nil { 1237 fmt.Printf("Could not parse expected storage in '%v': %v\n", allowanceExpectedStorage, err) 1238 continue 1239 } 1240 _, err = fmt.Sscan(es, &expectedStorage) 1241 if err != nil { 1242 fmt.Printf("Could not parse expected storage in '%v': %v\n", allowanceExpectedStorage, err) 1243 continue 1244 } 1245 break 1246 } 1247 req = req.WithExpectedStorage(expectedStorage) 1248 1249 // expectedUpload 1250 fmt.Println() 1251 fmt.Println(`6/8: Expected Upload 1252 Expected upload tells siad how much uploading the user expects to do each 1253 period. If this value is high, siad will more strongly prefer hosts that have a 1254 low upload bandwidth price. If this value is low, siad will focus on other 1255 metrics than upload bandwidth pricing, because even if the host charges a lot 1256 for upload bandwidth, it will not impact the total cost to the user very much. 1257 1258 The user should not consider upload bandwidth used during repairs, siad will 1259 consider repair bandwidth separately. 1260 1261 The following units can be used to set the expected upload:`) 1262 fmt.Println() 1263 fmt.Printf(" %v\n", fileSizeUnits) 1264 fmt.Println() 1265 euCurrentPeriod := allowance.ExpectedUpload * uint64(allowance.Period) 1266 euDefaultPeriod := skymodules.DefaultAllowance.ExpectedUpload * uint64(skymodules.DefaultAllowance.Period) 1267 fmt.Println("Current value:", modules.FilesizeUnits(euCurrentPeriod)) 1268 fmt.Println("Default value:", modules.FilesizeUnits(euDefaultPeriod)) 1269 1270 if allowance.ExpectedUpload == 0 { 1271 fmt.Println("Enter desired value below, or leave blank to use default value") 1272 } else { 1273 fmt.Println("Enter desired value below, or leave blank to use current value") 1274 } 1275 var expectedUpload uint64 1276 for { 1277 fmt.Print("Expected Upload: ") 1278 allowanceExpectedUpload := readString() 1279 if allowanceExpectedUpload == "" { 1280 // The user did not enter a value so use either the default or the 1281 // current value, as appropriate. 1282 if allowance.ExpectedUpload == 0 { 1283 expectedUpload = euDefaultPeriod 1284 } else { 1285 expectedUpload = euCurrentPeriod 1286 } 1287 break 1288 } 1289 1290 eu, err := parseFilesize(allowanceExpectedUpload) 1291 if err != nil { 1292 fmt.Printf("Could not parse expected upload in '%v': %v\n", allowanceExpectedUpload, err) 1293 continue 1294 } 1295 _, err = fmt.Sscan(eu, &expectedUpload) 1296 if err != nil { 1297 fmt.Printf("Could not parse expected upload in '%v': %v\n", allowanceExpectedUpload, err) 1298 continue 1299 } 1300 break 1301 } 1302 // User set field in terms of period, need to normalize to per-block. 1303 expectedUpload /= uint64(period) 1304 req = req.WithExpectedUpload(expectedUpload) 1305 1306 // expectedDownload 1307 fmt.Println() 1308 fmt.Println(`7/8: Expected Download 1309 Expected download tells siad how much downloading the user expects to do each 1310 period. If this value is high, siad will more strongly prefer hosts that have a 1311 low download bandwidth price. If this value is low, siad will focus on other 1312 metrics than download bandwidth pricing, because even if the host charges a lot 1313 for downloads, it will not impact the total cost to the user very much. 1314 1315 The user should not consider download bandwidth used during repairs, siad will 1316 consider repair bandwidth separately. 1317 1318 The following units can be used to set the expected download:`) 1319 fmt.Println() 1320 fmt.Printf(" %v\n", fileSizeUnits) 1321 fmt.Println() 1322 edCurrentPeriod := allowance.ExpectedDownload * uint64(allowance.Period) 1323 edDefaultPeriod := skymodules.DefaultAllowance.ExpectedDownload * uint64(skymodules.DefaultAllowance.Period) 1324 fmt.Println("Current value:", modules.FilesizeUnits(edCurrentPeriod)) 1325 fmt.Println("Default value:", modules.FilesizeUnits(edDefaultPeriod)) 1326 1327 if allowance.ExpectedDownload == 0 { 1328 fmt.Println("Enter desired value below, or leave blank to use default value") 1329 } else { 1330 fmt.Println("Enter desired value below, or leave blank to use current value") 1331 } 1332 var expectedDownload uint64 1333 for { 1334 fmt.Print("Expected Download: ") 1335 allowanceExpectedDownload := readString() 1336 if allowanceExpectedDownload == "" { 1337 // The user did not enter a value so use either the default or the 1338 // current value, as appropriate. 1339 if allowance.ExpectedDownload == 0 { 1340 expectedDownload = edDefaultPeriod 1341 } else { 1342 expectedDownload = edCurrentPeriod 1343 } 1344 break 1345 } 1346 1347 ed, err := parseFilesize(allowanceExpectedDownload) 1348 if err != nil { 1349 fmt.Printf("Could not parse expected download in '%v': %v\n", allowanceExpectedDownload, err) 1350 continue 1351 } 1352 _, err = fmt.Sscan(ed, &expectedDownload) 1353 if err != nil { 1354 fmt.Printf("Could not parse expected download in '%v': %v\n", allowanceExpectedDownload, err) 1355 continue 1356 } 1357 break 1358 } 1359 // User set field in terms of period, need to normalize to per-block. 1360 expectedDownload /= uint64(period) 1361 req = req.WithExpectedDownload(expectedDownload) 1362 1363 // expectedRedundancy 1364 fmt.Println() 1365 fmt.Println(`8/8: Expected Redundancy 1366 Expected redundancy is used in conjunction with expected storage to determine 1367 the total amount of raw storage that will be stored on hosts. If the expected 1368 storage is 1 TB and the expected redundancy is 3, then the renter will calculate 1369 that the total amount of storage in the user's contracts will be 3 TiB. 1370 1371 This value does not need to be changed from the default unless the user is 1372 manually choosing redundancy settings for their file. If different files are 1373 being given different redundancy settings, then the average of all the 1374 redundancies should be used as the value for expected redundancy, weighted by 1375 how large the files are.`) 1376 fmt.Println() 1377 fmt.Println("Current value:", allowance.ExpectedRedundancy) 1378 fmt.Println("Default value:", skymodules.DefaultAllowance.ExpectedRedundancy) 1379 1380 var expectedRedundancy float64 1381 var err error 1382 if allowance.ExpectedRedundancy == 0 { 1383 expectedRedundancy = skymodules.DefaultAllowance.ExpectedRedundancy 1384 fmt.Println("Enter desired value below, or leave blank to use default value") 1385 } else { 1386 expectedRedundancy = allowance.ExpectedRedundancy 1387 fmt.Println("Enter desired value below, or leave blank to use current value") 1388 } 1389 for { 1390 fmt.Print("Expected Redundancy: ") 1391 allowanceExpectedRedundancy := readString() 1392 if allowanceExpectedRedundancy == "" { 1393 break 1394 } 1395 1396 expectedRedundancy, err = strconv.ParseFloat(allowanceExpectedRedundancy, 64) 1397 if err != nil { 1398 fmt.Printf("Could not parse expected redundancy in '%v': %v\n", allowanceExpectedRedundancy, err) 1399 continue 1400 } 1401 if expectedRedundancy < 1 { 1402 fmt.Println("Expected redundancy must be at least 1") 1403 continue 1404 } 1405 break 1406 } 1407 req = req.WithExpectedRedundancy(expectedRedundancy) 1408 fmt.Println() 1409 1410 return req 1411 } 1412 1413 // renterbubblecmd is the handler for the command `skyc renter 1414 // bubble`. 1415 func renterbubblecmd(directory string) { 1416 // Parse the siapath 1417 var siaPath skymodules.SiaPath 1418 if directory == "." { 1419 directory = "root" // For UX 1420 siaPath = skymodules.RootSiaPath() 1421 } else { 1422 err := siaPath.LoadString(directory) 1423 if err != nil { 1424 die("Unable to load siapath:", err) 1425 } 1426 } 1427 fmt.Println("Calling bubble on:", directory) 1428 1429 // Bubble Directory 1430 err := httpClient.RenterBubblePost(siaPath, renterBubbleAll) 1431 if err != nil { 1432 die("Unable to bubble", directory, ":", err) 1433 } 1434 fmt.Println("Bubble successful!") 1435 } 1436 1437 // renterbackcreatecmd is the handler for the command `skyc renter 1438 // createbackup`. 1439 func renterbackupcreatecmd(name string) { 1440 // Create backup. 1441 err := httpClient.RenterCreateBackupPost(name) 1442 if err != nil { 1443 die("Failed to create backup", err) 1444 } 1445 fmt.Println("Backup initiated. Monitor progress with the 'listbackups' command.") 1446 } 1447 1448 // renterbackuprestorecmd is the handler for the command `skyc renter 1449 // restorebackup`. 1450 func renterbackuprestorecmd(name string) { 1451 err := httpClient.RenterRecoverBackupPost(name) 1452 if err != nil { 1453 die("Failed to restore backup", err) 1454 } 1455 } 1456 1457 // renterbackuplistcmd is the handler for the command `skyc renter listbackups`. 1458 func renterbackuplistcmd() { 1459 ubs, err := httpClient.RenterBackups() 1460 if err != nil { 1461 die("Failed to retrieve backups", err) 1462 } else if len(ubs.Backups) == 0 { 1463 fmt.Println("No uploaded backups.") 1464 return 1465 } 1466 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 1467 fmt.Fprintln(w, " Name\tCreation Date\tUpload Progress") 1468 for _, ub := range ubs.Backups { 1469 date := time.Unix(int64(ub.CreationDate), 0) 1470 fmt.Fprintf(w, " %v\t%v\t%v\n", ub.Name, date.Format(time.ANSIC), ub.UploadProgress) 1471 } 1472 if err := w.Flush(); err != nil { 1473 die("failed to flush writer:", err) 1474 } 1475 } 1476 1477 // rentercontractscmd is the handler for the command `skyc renter contracts`. 1478 // It lists the Renter's contracts. 1479 func rentercontractscmd() { 1480 rc, err := httpClient.RenterDisabledContractsGet() 1481 if err != nil { 1482 die("Could not get contracts:", err) 1483 } 1484 1485 // Build Current Period summary 1486 fmt.Println("Current Period Summary") 1487 // Active Contracts are all good data 1488 activeSize, activeSpent, activeRemaining, activeFees := contractStats(rc.ActiveContracts) 1489 // Passive Contracts are all good data 1490 passiveSize, passiveSpent, passiveRemaining, passiveFees := contractStats(rc.PassiveContracts) 1491 // Refreshed Contracts are duplicate data 1492 _, refreshedSpent, refreshedRemaining, refreshedFees := contractStats(rc.RefreshedContracts) 1493 // Disabled Contracts are wasted data 1494 disabledSize, disabledSpent, disabledRemaining, disabledFees := contractStats(rc.DisabledContracts) 1495 // Sum up the appropriate totals 1496 totalStored := activeSize + passiveSize 1497 totalWasted := disabledSize 1498 totalSpent := activeSpent.Add(passiveSpent).Add(refreshedSpent).Add(disabledSpent) 1499 totalRemaining := activeRemaining.Add(passiveRemaining).Add(refreshedRemaining).Add(disabledRemaining) 1500 totalFees := activeFees.Add(passiveFees).Add(refreshedFees).Add(disabledFees) 1501 1502 fmt.Printf(` Total Good Data: %s 1503 Total Wasted Data: %s 1504 Total Remaining: %v 1505 Total Spent: %v 1506 Total Fees: %v 1507 1508 `, modules.FilesizeUnits(totalStored), modules.FilesizeUnits(totalWasted), currencyUnits(totalRemaining), currencyUnits(totalSpent), currencyUnits(totalFees)) 1509 1510 // List out contracts 1511 fmt.Println("Active Contracts:") 1512 if len(rc.ActiveContracts) == 0 { 1513 fmt.Println(" No active contracts.") 1514 } else { 1515 // Display Active Contracts 1516 writeContracts(rc.ActiveContracts) 1517 } 1518 1519 fmt.Println("\nPassive Contracts:") 1520 if len(rc.PassiveContracts) == 0 { 1521 fmt.Println(" No passive contracts.") 1522 } else { 1523 // Display Passive Contracts 1524 writeContracts(rc.PassiveContracts) 1525 } 1526 1527 fmt.Println("\nRefreshed Contracts:") 1528 if len(rc.RefreshedContracts) == 0 { 1529 fmt.Println(" No refreshed contracts.") 1530 } else { 1531 // Display Refreshed Contracts 1532 writeContracts(rc.RefreshedContracts) 1533 } 1534 1535 fmt.Println("\nDisabled Contracts:") 1536 if len(rc.DisabledContracts) == 0 { 1537 fmt.Println(" No disabled contracts.") 1538 } else { 1539 // Display Disabled Contracts 1540 writeContracts(rc.DisabledContracts) 1541 } 1542 1543 if renterAllContracts { 1544 rce, err := httpClient.RenterExpiredContractsGet() 1545 if err != nil { 1546 die("Could not get expired contracts:", err) 1547 } 1548 // Build Historical summary 1549 fmt.Println("\nHistorical Summary") 1550 // Expired Contracts are all good data 1551 expiredSize, expiredSpent, expiredRemaining, expiredFees := contractStats(rce.ExpiredContracts) 1552 // Expired Refreshed Contracts are duplicate data 1553 _, expiredRefreshedSpent, expiredRefreshedRemaining, expiredRefreshedFees := contractStats(rce.ExpiredRefreshedContracts) 1554 // Sum up the appropriate totals 1555 totalStored := expiredSize 1556 totalSpent := expiredSpent.Add(expiredRefreshedSpent) 1557 totalRemaining := expiredRemaining.Add(expiredRefreshedRemaining) 1558 totalFees := expiredFees.Add(expiredRefreshedFees) 1559 1560 fmt.Printf(` Total Expired Data: %s 1561 Total Remaining: %v 1562 Total Spent: %v 1563 Total Fees: %v 1564 1565 `, modules.FilesizeUnits(totalStored), currencyUnits(totalRemaining), currencyUnits(totalSpent), currencyUnits(totalFees)) 1566 fmt.Println("\nExpired Contracts:") 1567 if len(rce.ExpiredContracts) == 0 { 1568 fmt.Println(" No expired contracts.") 1569 } else { 1570 writeContracts(rce.ExpiredContracts) 1571 } 1572 1573 fmt.Println("\nExpired Refresh Contracts:") 1574 if len(rce.ExpiredRefreshedContracts) == 0 { 1575 fmt.Println(" No expired refreshed contracts.") 1576 } else { 1577 writeContracts(rce.ExpiredRefreshedContracts) 1578 } 1579 } 1580 } 1581 1582 // rentercontractsviewcmd is the handler for the command `skyc renter contracts <id>`. 1583 // It lists details of a specific contract. 1584 func rentercontractsviewcmd(cid string) { 1585 rc, err := httpClient.RenterAllContractsGet() 1586 if err != nil { 1587 die("Could not get contract details: ", err) 1588 } 1589 1590 contracts := append(rc.ActiveContracts, rc.PassiveContracts...) 1591 contracts = append(contracts, rc.RefreshedContracts...) 1592 contracts = append(contracts, rc.DisabledContracts...) 1593 contracts = append(contracts, rc.ExpiredContracts...) 1594 contracts = append(contracts, rc.ExpiredRefreshedContracts...) 1595 1596 err = printContractInfo(cid, contracts) 1597 if err != nil { 1598 die(err) 1599 } 1600 } 1601 1602 // renterfilesdownload downloads the dir at the given path from the Sia network 1603 // to the local specified destination. 1604 func renterdirdownload(path, destination string) { 1605 destination = abs(destination) 1606 // Parse SiaPath. 1607 siaPath, err := skymodules.NewSiaPath(path) 1608 if err != nil { 1609 die("Couldn't parse SiaPath:", err) 1610 } 1611 // If root is not set we need to rebase. 1612 if !renterDownloadRoot { 1613 siaPath, err = siaPath.Rebase(skymodules.RootSiaPath(), skymodules.UserFolder) 1614 if err != nil { 1615 die("Couldn't rebase SiaPath:", err) 1616 } 1617 } 1618 // Download dir. 1619 start := time.Now() 1620 tfs, skipped, totalSize, downloadErr := downloadDir(siaPath, destination) 1621 if renterDownloadAsync && downloadErr != nil { 1622 fmt.Println("At least one error occurred when initializing the download:", downloadErr) 1623 } 1624 // If the download is async, report success. 1625 if renterDownloadAsync { 1626 fmt.Printf("Queued Download '%s' to %s.\n", siaPath.String(), abs(destination)) 1627 return 1628 } 1629 // If the download is blocking, display progress as the file downloads. 1630 failedDownloads := downloadProgress(tfs) 1631 // Print skipped files. 1632 for _, s := range skipped { 1633 fmt.Printf("Skipped file '%v' since it already exists\n", s) 1634 } 1635 // Handle potential errors. 1636 if len(failedDownloads) == 0 { 1637 fmt.Printf("\nDownloaded '%s' to '%s - %v in %v'.\n", path, abs(destination), modules.FilesizeUnits(totalSize), time.Since(start).Round(time.Millisecond)) 1638 return 1639 } 1640 // Print errors. 1641 if downloadErr != nil { 1642 fmt.Println("At least one error occurred when initializing the download:", downloadErr) 1643 } 1644 for _, fd := range failedDownloads { 1645 fmt.Printf("Download of file '%v' to destination '%v' failed: %v\n", fd.SiaPath, fd.Destination, fd.Error) 1646 } 1647 os.Exit(1) 1648 } 1649 1650 // renterdownloadcancelcmd is the handler for the command `skyc renter download cancel [cancelID]` 1651 // Cancels the ongoing download. 1652 func renterdownloadcancelcmd(cancelID skymodules.DownloadID) { 1653 if err := httpClient.RenterCancelDownloadPost(cancelID); err != nil { 1654 die("Couldn't cancel download:", err) 1655 } 1656 fmt.Println("Download canceled successfully") 1657 } 1658 1659 // renterfilesdeletecmd is the handler for the command `skyc renter delete [path]`. 1660 // Removes the specified path from the Sia network. 1661 func renterfilesdeletecmd(cmd *cobra.Command, paths []string) { 1662 for _, path := range paths { 1663 // Parse SiaPath. 1664 siaPath, err := skymodules.NewSiaPath(path) 1665 if err != nil { 1666 die("Couldn't parse SiaPath:", err) 1667 } 1668 1669 // Try to delete file. 1670 // 1671 // In the case where the path points to a dir, this will fail and we 1672 // silently move on to deleting it as a dir. This is more efficient than 1673 // querying the renter first to see if it is a file or a dir, as that is 1674 // guaranteed to always be two renter calls. 1675 var errFile error 1676 if renterDeleteRoot { 1677 errFile = httpClient.RenterFileDeleteRootPost(siaPath) 1678 } else { 1679 errFile = httpClient.RenterFileDeletePost(siaPath) 1680 } 1681 if errFile == nil { 1682 fmt.Printf("Deleted file '%v'\n", path) 1683 continue 1684 } else if !(strings.Contains(errFile.Error(), filesystem.ErrNotExist.Error()) || strings.Contains(errFile.Error(), filesystem.ErrDeleteFileIsDir.Error())) { 1685 die(fmt.Sprintf("Failed to delete file %v: %v", path, errFile)) 1686 } 1687 // Try to delete dir. 1688 var errDir error 1689 if renterDeleteRoot { 1690 errDir = httpClient.RenterDirDeleteRootPost(siaPath) 1691 } else { 1692 errDir = httpClient.RenterDirDeletePost(siaPath) 1693 } 1694 if errDir == nil { 1695 fmt.Printf("Deleted directory '%v'\n", path) 1696 continue 1697 } else if !strings.Contains(errDir.Error(), filesystem.ErrNotExist.Error()) { 1698 die(fmt.Sprintf("Failed to delete directory %v: %v", path, errDir)) 1699 } 1700 1701 // Unknown file/dir. 1702 die(fmt.Sprintf("Unknown path '%v'", path)) 1703 } 1704 return 1705 } 1706 1707 // renterfilesdownload is the handler for the command `skyc renter download 1708 // [path] [destination]`. It determines whether a file or a folder is downloaded 1709 // and calls the corresponding sub-handler. 1710 func renterfilesdownloadcmd(path, destination string) { 1711 // Parse SiaPath. 1712 siaPath, err := skymodules.NewSiaPath(path) 1713 if err != nil { 1714 die("Couldn't parse SiaPath:", err) 1715 } 1716 // If root is not set we need to rebase. 1717 if !renterDownloadRoot { 1718 siaPath, err = siaPath.Rebase(skymodules.RootSiaPath(), skymodules.UserFolder) 1719 if err != nil { 1720 die("Couldn't rebase SiaPath:", err) 1721 } 1722 } 1723 _, err = httpClient.RenterFileRootGet(siaPath) 1724 if err == nil { 1725 renterFilesDownload(path, destination) 1726 return 1727 } else if !strings.Contains(err.Error(), filesystem.ErrNotExist.Error()) { 1728 die("Failed to download file:", err) 1729 } 1730 _, err = httpClient.RenterDirRootGet(siaPath) 1731 if err == nil { 1732 renterdirdownload(path, destination) 1733 return 1734 } else if !strings.Contains(err.Error(), filesystem.ErrNotExist.Error()) { 1735 die("Failed to download folder:", err) 1736 } 1737 die(fmt.Sprintf("Unknown path '%v'", path)) 1738 } 1739 1740 // rentertriggercontractrecoveryrescancmd starts a new scan for recoverable 1741 // contracts on the blockchain. 1742 func rentertriggercontractrecoveryrescancmd() { 1743 crpg, err := httpClient.RenterContractRecoveryProgressGet() 1744 if err != nil { 1745 die("Failed to get recovery status", err) 1746 } 1747 if crpg.ScanInProgress { 1748 fmt.Println("Scan already in progress") 1749 fmt.Println("Scanned height:\t", crpg.ScannedHeight) 1750 return 1751 } 1752 if err := httpClient.RenterInitContractRecoveryScanPost(); err != nil { 1753 die("Failed to trigger recovery scan", err) 1754 } 1755 fmt.Println("Successfully triggered contract recovery scan.") 1756 } 1757 1758 // rentercontractrecoveryscanprogresscmd returns the current progress of a 1759 // potentially ongoing recovery scan. 1760 func rentercontractrecoveryscanprogresscmd() { 1761 crpg, err := httpClient.RenterContractRecoveryProgressGet() 1762 if err != nil { 1763 die("Failed to get recovery status", err) 1764 } 1765 if crpg.ScanInProgress { 1766 fmt.Println("Scan in progress") 1767 fmt.Println("Scanned height:\t", crpg.ScannedHeight) 1768 } else { 1769 fmt.Println("No scan in progress") 1770 } 1771 } 1772 1773 // renterfileslistcmd is the handler for the command `skyc renter ls`. Lists 1774 // files known to the renter on the network. 1775 func renterfileslistcmd(cmd *cobra.Command, args []string) { 1776 // Parse the SiaPath 1777 sp, err := parseLSArgs(args) 1778 if err != nil { 1779 fmt.Fprintln(os.Stderr, err) 1780 _ = cmd.UsageFunc()(cmd) 1781 os.Exit(exitCodeUsage) 1782 } 1783 1784 // Check for file first 1785 if !sp.IsRoot() { 1786 tryDir, err := printSingleFile(sp, renterListRoot, false) 1787 if err != nil { 1788 die(err) 1789 } 1790 if !tryDir { 1791 return 1792 } 1793 } 1794 1795 // Get dirs with their corresponding files. They will be sorted by siapath. 1796 dirs := getDirSorted(sp, renterListRoot, renterListRecursive, verbose) 1797 1798 // Get the total number of listings (subdirs and files). 1799 root := dirs[0] // Root directory we are querying. 1800 totalStored := root.dir.AggregateSize 1801 var numFilesDirs uint64 1802 if renterListRecursive { 1803 numFilesDirs = root.dir.AggregateNumFiles + root.dir.AggregateNumSubDirs 1804 } else { 1805 numFilesDirs = root.dir.NumFiles + root.dir.NumSubDirs 1806 } 1807 1808 // Print totals for both verbose and not verbose output. 1809 totalStoredStr := modules.FilesizeUnits(totalStored) 1810 fmt.Printf("\nListing %v files/dirs:\t%9s\n\n", numFilesDirs, totalStoredStr) 1811 1812 // Handle the non verbose output. 1813 if !verbose { 1814 err := printDirs(dirs) 1815 if err != nil { 1816 die(err) 1817 } 1818 return 1819 } 1820 1821 // Handle the verbose output. 1822 err = printDirsVerbose(dirs) 1823 if err != nil { 1824 die(err) 1825 } 1826 } 1827 1828 // renterfilesrenamecmd is the handler for the command `skyc renter rename [path] [newpath]`. 1829 // Renames a file on the Sia network. 1830 func renterfilesrenamecmd(path, newpath string) { 1831 // Parse SiaPath. 1832 siaPath, err1 := skymodules.NewSiaPath(path) 1833 newSiaPath, err2 := skymodules.NewSiaPath(newpath) 1834 if err := errors.Compose(err1, err2); err != nil { 1835 die("Couldn't parse SiaPath:", err) 1836 } 1837 err := httpClient.RenterRenamePost(siaPath, newSiaPath, renterRenameRoot) 1838 if err != nil { 1839 die("Could not rename file:", err) 1840 } 1841 fmt.Printf("Renamed %s to %s\n", path, newpath) 1842 } 1843 1844 // renterfusecmd displays the list of directories that are currently mounted via 1845 // fuse. 1846 func renterfusecmd() { 1847 // Get the list of mountpoints. 1848 fuseInfo, err := httpClient.RenterFuse() 1849 if err != nil { 1850 die("Unable to fetch fuse information:", err) 1851 } 1852 mountPoints := fuseInfo.MountPoints 1853 1854 // Special message if nothing is mounted. 1855 if len(mountPoints) == 0 { 1856 fmt.Println("Nothing mounted.") 1857 return 1858 } 1859 1860 // Sort the mountpoints. 1861 sort.Slice(mountPoints, func(i, j int) bool { 1862 return strings.Compare(mountPoints[i].MountPoint, mountPoints[j].MountPoint) < 0 1863 }) 1864 1865 // Print out the sorted set of mountpoints. 1866 fmt.Println("Mounted folders:") 1867 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 1868 fmt.Fprintf(w, "\t%s\t%s\n", "Mount Point", "SiaPath") 1869 for _, mp := range mountPoints { 1870 siaPathStr := mp.SiaPath.String() 1871 if siaPathStr == "" { 1872 siaPathStr = "{root}" 1873 } 1874 1875 fmt.Fprintf(w, "\t%s\t%s\n", mp.MountPoint, siaPathStr) 1876 } 1877 if err := w.Flush(); err != nil { 1878 die("failed to flush writer:", err) 1879 } 1880 fmt.Println() 1881 } 1882 1883 // renterfusemountcmd is the handler for the command `skyc renter fuse mount [path] [siapath]`. 1884 func renterfusemountcmd(path, siaPathStr string) { 1885 // TODO: Once read-write is supported on the backend, the 'true' flag can be 1886 // set to 'false' - siac will support mounting read-write by default. Need 1887 // to update the help string of the command to indicate that mounting will 1888 // mount in read-write mode. 1889 path = abs(path) 1890 var siaPath skymodules.SiaPath 1891 var err error 1892 if siaPathStr == "" || siaPathStr == "/" { 1893 siaPath = skymodules.RootSiaPath() 1894 } else { 1895 siaPath, err = skymodules.NewSiaPath(siaPathStr) 1896 if err != nil { 1897 die("Unable to parse the siapath that should be mounted:", err) 1898 } 1899 } 1900 opts := skymodules.MountOptions{ 1901 ReadOnly: true, 1902 AllowOther: renterFuseMountAllowOther, 1903 } 1904 err = httpClient.RenterFuseMount(path, siaPath, opts) 1905 if err != nil { 1906 die("Unable to mount the directory:", err) 1907 } 1908 fmt.Printf("mounted %s to %s\n", siaPathStr, path) 1909 } 1910 1911 // renterfuseunmountcmd is the handler for the command `skyc renter fuse unmount [path]`. 1912 func renterfuseunmountcmd(path string) { 1913 path = abs(path) 1914 err := httpClient.RenterFuseUnmount(path) 1915 if err != nil { 1916 s := fmt.Sprintf("Unable to unmount %s:", path) 1917 die(s, err) 1918 } 1919 fmt.Printf("Unmounted %s successfully\n", path) 1920 } 1921 1922 // rentersetlocalpathcmd is the handler for the command `skyc renter setlocalpath [siapath] [newlocalpath]` 1923 // Changes the trackingpath of the file 1924 // through API Endpoint 1925 func rentersetlocalpathcmd(siapath, newlocalpath string) { 1926 //Parse Siapath 1927 siaPath, err := skymodules.NewSiaPath(siapath) 1928 if err != nil { 1929 die("Couldn't parse Siapath:", err) 1930 } 1931 err = httpClient.RenterSetRepairPathPost(siaPath, newlocalpath) 1932 if err != nil { 1933 die("Could not Change the path of the file:", err) 1934 } 1935 fmt.Printf("Updated %s localpath to %s\n", siapath, newlocalpath) 1936 } 1937 1938 // renterfilesunstuckcmd is the handler for the command `skyc renter 1939 // unstuckall`. Sets all files to unstuck. 1940 func renterfilesunstuckcmd() { 1941 // Get all dirs and their files recursively. 1942 dirs := getDir(skymodules.RootSiaPath(), true, true, verbose) 1943 1944 // Count all files. 1945 totalFiles := 0 1946 for _, d := range dirs { 1947 totalFiles += len(d.files) 1948 } 1949 1950 // Declare a worker function to mark files as not stuck. 1951 var atomicFilesDone uint64 1952 toUnstuck := make(chan skymodules.SiaPath) 1953 worker := func() { 1954 for siaPath := range toUnstuck { 1955 err := httpClient.RenterSetFileStuckPost(siaPath, true, false) 1956 if err != nil { 1957 die(fmt.Sprintf("Couldn't set %v to unstuck: %v", siaPath, err)) 1958 } 1959 atomic.AddUint64(&atomicFilesDone, 1) 1960 } 1961 } 1962 // Spin up some workers. 1963 var wg sync.WaitGroup 1964 for i := 0; i < 20; i++ { 1965 wg.Add(1) 1966 go func() { 1967 defer wg.Done() 1968 worker() 1969 }() 1970 } 1971 // Pass the files on to the workers. 1972 lastStatusUpdate := time.Now() 1973 for _, d := range dirs { 1974 for _, f := range d.files { 1975 if !f.Stuck && f.NumStuckChunks == 0 { 1976 // Nothing to do. Count as set for progress. 1977 atomic.AddUint64(&atomicFilesDone, 1) 1978 continue 1979 } 1980 toUnstuck <- f.SiaPath 1981 if time.Since(lastStatusUpdate) > time.Second { 1982 fmt.Printf("\r%v of %v files set to 'unstuck'", 1983 atomic.LoadUint64(&atomicFilesDone), totalFiles) 1984 lastStatusUpdate = time.Now() 1985 } 1986 } 1987 } 1988 close(toUnstuck) 1989 wg.Wait() 1990 fmt.Println("\nSet all files to 'unstuck'") 1991 } 1992 1993 // renterfilesuploadcmd is the handler for the command `skyc renter upload 1994 // [source] [path]`. Uploads the [source] file to [path] on the Sia network. 1995 // If [source] is a directory, all files inside it will be uploaded and named 1996 // relative to [path]. 1997 func renterfilesuploadcmd(source, path string) { 1998 stat, err := os.Stat(source) 1999 if err != nil { 2000 die("Could not stat file or folder:", err) 2001 } 2002 2003 // Check for and parse any redundancy settings 2004 numDataPieces, numParityPieces, err := api.ParseDataAndParityPieces(dataPieces, parityPieces) 2005 if err != nil { 2006 die("Could not parse data and parity pieces:", err) 2007 } 2008 2009 if stat.IsDir() { 2010 // folder 2011 var files []string 2012 err := filepath.Walk(source, func(path string, info os.FileInfo, err error) error { 2013 if err != nil { 2014 fmt.Println("Warning: skipping file:", err) 2015 return nil 2016 } 2017 if info.IsDir() { 2018 return nil 2019 } 2020 files = append(files, path) 2021 return nil 2022 }) 2023 if err != nil { 2024 die("Could not read folder:", err) 2025 } else if len(files) == 0 { 2026 die("Nothing to upload.") 2027 } 2028 failed := 0 2029 for _, file := range files { 2030 fpath, _ := filepath.Rel(source, file) 2031 fpath = filepath.Join(path, fpath) 2032 fpath = filepath.ToSlash(fpath) 2033 // Parse SiaPath. 2034 fSiaPath, err := skymodules.NewSiaPath(fpath) 2035 if err != nil { 2036 die("Couldn't parse SiaPath:", err) 2037 } 2038 err = httpClient.RenterUploadPost(abs(file), fSiaPath, uint64(numDataPieces), uint64(numParityPieces)) 2039 if err != nil { 2040 failed++ 2041 fmt.Printf("Could not upload file %s :%v\n", file, err) 2042 } 2043 } 2044 fmt.Printf("\nUploaded %d of %d files into '%s'.\n", len(files)-failed, len(files), path) 2045 } else { 2046 // single file 2047 // Parse SiaPath. 2048 siaPath, err := skymodules.NewSiaPath(path) 2049 if err != nil { 2050 die("Couldn't parse SiaPath:", err) 2051 } 2052 err = httpClient.RenterUploadPost(abs(source), siaPath, uint64(numDataPieces), uint64(numParityPieces)) 2053 if err != nil { 2054 die("Could not upload file:", err) 2055 } 2056 fmt.Printf("Uploaded '%s' as '%s'.\n", abs(source), path) 2057 } 2058 } 2059 2060 // renterfilesuploadpausecmd is the handler for the command `skyc renter upload 2061 // pause`. It pauses all renter uploads for the duration (in minutes) 2062 // passed in. 2063 func renterfilesuploadpausecmd(dur string) { 2064 pauseDuration, err := time.ParseDuration(dur) 2065 if err != nil { 2066 die("Couldn't parse duration:", err) 2067 } 2068 err = httpClient.RenterUploadsPausePost(pauseDuration) 2069 if err != nil { 2070 die("Could not pause renter uploads:", err) 2071 } 2072 fmt.Println("Renter uploads have been paused for", dur) 2073 } 2074 2075 // renterfilesuploadresumecmd is the handler for the command `skyc renter upload 2076 // resume`. It resumes all renter uploads that have been paused. 2077 func renterfilesuploadresumecmd() { 2078 err := httpClient.RenterUploadsResumePost() 2079 if err != nil { 2080 die("Could not resume renter uploads:", err) 2081 } 2082 fmt.Println("Renter uploads have been resumed") 2083 } 2084 2085 // renterpricescmd is the handler for the command `skyc renter prices`, which 2086 // displays the prices of various storage operations. The user can submit an 2087 // allowance to have the estimate reflect those settings or the user can submit 2088 // nothing 2089 func renterpricescmd(cmd *cobra.Command, args []string) { 2090 allowance := skymodules.Allowance{} 2091 2092 if len(args) != 0 && len(args) != 4 { 2093 _ = cmd.UsageFunc()(cmd) 2094 os.Exit(exitCodeUsage) 2095 } 2096 if len(args) > 0 { 2097 hastings, err := types.ParseCurrency(args[0]) 2098 if err != nil { 2099 die("Could not parse amount:", err) 2100 } 2101 blocks, err := parsePeriod(args[1]) 2102 if err != nil { 2103 die("Could not parse period:", err) 2104 } 2105 _, err = fmt.Sscan(hastings, &allowance.Funds) 2106 if err != nil { 2107 die("Could not set allowance funds:", err) 2108 } 2109 2110 _, err = fmt.Sscan(blocks, &allowance.Period) 2111 if err != nil { 2112 die("Could not set allowance period:", err) 2113 } 2114 hosts, err := strconv.Atoi(args[2]) 2115 if err != nil { 2116 die("Could not parse host count") 2117 } 2118 allowance.Hosts = uint64(hosts) 2119 renewWindow, err := parsePeriod(args[3]) 2120 if err != nil { 2121 die("Could not parse renew window") 2122 } 2123 _, err = fmt.Sscan(renewWindow, &allowance.RenewWindow) 2124 if err != nil { 2125 die("Could not set allowance renew window:", err) 2126 } 2127 } 2128 2129 rpg, err := httpClient.RenterPricesGet(allowance) 2130 if err != nil { 2131 die("Could not read the renter prices:", err) 2132 } 2133 periodFactor := uint64(rpg.Allowance.Period / types.BlocksPerMonth) 2134 2135 // Display Estimate 2136 rate, err := types.ParseExchangeRate(build.ExchangeRate()) 2137 if err != nil { 2138 fmt.Printf("Warning: ignoring exchange rate - %s\n", err) 2139 } 2140 2141 fmt.Println("Renter Prices (estimated):") 2142 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 2143 fmt.Fprintln(w, "\tFees for Creating a Set of Contracts:\t", currencyUnitsWithExchangeRate(rpg.FormContracts, rate)) 2144 fmt.Fprintln(w, "\tDownload 1 TB:\t", currencyUnitsWithExchangeRate(rpg.DownloadTerabyte, rate)) 2145 fmt.Fprintln(w, "\tStore 1 TB for 1 Month:\t", currencyUnitsWithExchangeRate(rpg.StorageTerabyteMonth, rate)) 2146 fmt.Fprintln(w, "\tStore 1 TB for Allowance Period:\t", currencyUnitsWithExchangeRate(rpg.StorageTerabyteMonth.Mul64(periodFactor), rate)) 2147 fmt.Fprintln(w, "\tUpload 1 TB:\t", currencyUnitsWithExchangeRate(rpg.UploadTerabyte, rate)) 2148 if err := w.Flush(); err != nil { 2149 die("failed to flush writer:", err) 2150 } 2151 2152 // Display allowance used for estimate 2153 fmt.Println("\nAllowance used for estimate:") 2154 fmt.Fprintln(w, "\tFunds:\t", currencyUnitsWithExchangeRate(rpg.Allowance.Funds, rate)) 2155 fmt.Fprintln(w, "\tPeriod:\t", rpg.Allowance.Period) 2156 fmt.Fprintln(w, "\tHosts:\t", rpg.Allowance.Hosts) 2157 fmt.Fprintln(w, "\tRenew Window:\t", rpg.Allowance.RenewWindow) 2158 if err := w.Flush(); err != nil { 2159 die("failed to flush writer:", err) 2160 } 2161 } 2162 2163 // renterratelimitcmd is the handler for the command `skyc renter ratelimit` 2164 // which sets the maxuploadspeed and maxdownloadspeed in bytes-per-second for 2165 // the renter module 2166 func renterratelimitcmd(downloadSpeedStr, uploadSpeedStr string) { 2167 downloadSpeedInt, err := parseRatelimit(downloadSpeedStr) 2168 if err != nil { 2169 die(errors.AddContext(err, "unable to parse download speed")) 2170 } 2171 uploadSpeedInt, err := parseRatelimit(uploadSpeedStr) 2172 if err != nil { 2173 die(errors.AddContext(err, "unable to parse upload speed")) 2174 } 2175 2176 err = httpClient.RenterRateLimitPost(downloadSpeedInt, uploadSpeedInt) 2177 if err != nil { 2178 die(errors.AddContext(err, "Could not set renter ratelimit speed")) 2179 } 2180 fmt.Println("Set renter maxdownloadspeed to ", downloadSpeedInt, " and maxuploadspeed to ", uploadSpeedInt) 2181 } 2182 2183 // renterworkerscmd is the handler for the command `skyc renter workers`. 2184 // It lists the Renter's workers. 2185 func renterworkerscmd() { 2186 rw, err := httpClient.RenterWorkersSortedGet(true) 2187 if err != nil { 2188 die("Could not get workers:", err) 2189 } 2190 2191 // Print Worker Pool Summary 2192 fmt.Println("Worker Pool Summary") 2193 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 2194 fmt.Fprintf(w, " Total Workers:\t%v\n", rw.NumWorkers) 2195 fmt.Fprintf(w, " Workers On Download Cooldown:\t%v\n", rw.TotalDownloadCoolDown) 2196 fmt.Fprintf(w, " Workers On HasSector Cooldown:\t%v\n", rw.TotalHasSectorCoolDown) 2197 fmt.Fprintf(w, " Workers On Maintenance Cooldown:\t%v\n", rw.TotalMaintenanceCoolDown) 2198 fmt.Fprintf(w, " Workers On Repair Cooldown:\t%v\n", rw.TotalLowPrioDownloadCoolDown) 2199 fmt.Fprintf(w, " Workers On Upload Cooldown:\t%v\n", rw.TotalUploadCoolDown) 2200 if err := w.Flush(); err != nil { 2201 die("failed to flush writer:", err) 2202 } 2203 2204 // Split Workers into GoodForUpload and !GoodForUpload 2205 var goodForUpload, notGoodForUpload []skymodules.WorkerStatus 2206 for _, worker := range rw.Workers { 2207 if worker.ContractUtility.GoodForUpload { 2208 goodForUpload = append(goodForUpload, worker) 2209 continue 2210 } 2211 notGoodForUpload = append(notGoodForUpload, worker) 2212 } 2213 2214 // List out GoorForUpload workers 2215 fmt.Println("GoodForUpload Workers:") 2216 if len(goodForUpload) == 0 { 2217 fmt.Println(" No GoodForUpload workers.") 2218 } else { 2219 writeWorkers(goodForUpload) 2220 } 2221 2222 // List out !GoorForUpload workers 2223 fmt.Println("\nNot GoodForUpload Workers:") 2224 if len(notGoodForUpload) == 0 { 2225 fmt.Println(" All workers are GoodForUpload.") 2226 } else { 2227 writeWorkers(notGoodForUpload) 2228 } 2229 } 2230 2231 // renterworkerseacmd is the handler for the command `skyc renter workers ea`. 2232 // It lists the status of the account of every worker. 2233 func renterworkerseacmd() { 2234 rw, err := httpClient.RenterWorkersSortedGet(true) 2235 if err != nil { 2236 die("Could not get workers:", err) 2237 } 2238 2239 // collect some overal account stats 2240 var nfw uint64 2241 for _, worker := range rw.Workers { 2242 if worker.AccountStatus.AvailableBalance.IsZero() { 2243 nfw++ 2244 } 2245 } 2246 fmt.Println("Worker Accounts Summary") 2247 2248 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 2249 defer func() { 2250 err := w.Flush() 2251 if err != nil { 2252 die("Could not flush tabwriter:", err) 2253 } 2254 }() 2255 2256 // print summary 2257 fmt.Fprintf(w, "Total Workers: \t%v\n", rw.NumWorkers) 2258 fmt.Fprintf(w, "Non Funded Workers: \t%v\n", nfw) 2259 2260 // print account balance target 2261 if len(rw.Workers) > 0 { 2262 fmt.Fprintf(w, "Account Balance Target: \t%v\n", rw.Workers[0].AccountBalanceTarget.HumanString()) 2263 } 2264 2265 // print header 2266 hostInfo := "Host PubKey" 2267 accountInfo := "\tAvailBal\tNegBal\tHostBal\tSyncAt\tForcedSyncAt" 2268 errorInfo := "\tRefilledAt\tErrorAt\tError" 2269 header := hostInfo + accountInfo + errorInfo 2270 fmt.Fprintln(w, "\nWorker Accounts Detail \n\n"+header) 2271 2272 // print rows 2273 for _, worker := range rw.Workers { 2274 as := worker.AccountStatus 2275 2276 // Host Info 2277 fmt.Fprintf(w, "%v", worker.HostPubKey.String()) 2278 2279 // Account Info 2280 fmt.Fprintf(w, "\t%s\t%s\t%s\t%s\t%s", 2281 as.AvailableBalance.HumanString(), 2282 as.NegativeBalance.HumanString(), 2283 as.HostBalance.HumanString(), 2284 sanitizeTime(as.SyncAt, !as.SyncAt.IsZero()), 2285 sanitizeTime(as.ForcedSyncAt, !as.ForcedSyncAt.IsZero())) 2286 2287 // Error Info 2288 fmt.Fprintf(w, "\t%v\t%v\t%v\n", 2289 sanitizeTime(as.RecentSuccessTime, !as.RecentSuccessTime.IsZero()), 2290 sanitizeTime(as.RecentErrTime, as.RecentErr != ""), 2291 sanitizeErr(as.RecentErr)) 2292 } 2293 } 2294 2295 // renterworkersdownloadscmd is the handler for the command `skyc renter workers 2296 // dj`. It lists the status of the download jobs of every worker. 2297 func renterworkersdownloadscmd() { 2298 rw, err := httpClient.RenterWorkersSortedGet(true) 2299 if err != nil { 2300 die("Could not get workers:", err) 2301 } 2302 2303 // Create tab writer 2304 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 2305 defer func() { 2306 err := w.Flush() 2307 if err != nil { 2308 die("Could not flush tabwriter:", err) 2309 } 2310 }() 2311 2312 // Write Download Info 2313 writeWorkerDownloadInfo(w, rw) 2314 } 2315 2316 // renterworkersrepairscmd is the handler for the command `skyc renter workers 2317 // repj`. It lists the status of the low prio download jobs (used for repairs) of 2318 // every worker. 2319 func renterworkersrepairscmd() { 2320 rw, err := httpClient.RenterWorkersSortedGet(true) 2321 if err != nil { 2322 die("Could not get workers:", err) 2323 } 2324 2325 // Create tab writer 2326 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 2327 defer func() { 2328 err := w.Flush() 2329 if err != nil { 2330 die("Could not flush tabwriter:", err) 2331 } 2332 }() 2333 2334 // Write Reapir Info 2335 writeWorkerRepairInfo(w, rw) 2336 } 2337 2338 // renterworkersmaintenancecmd is the handler for the command `skyc renter 2339 // workers main`. It lists the status of the maintenance for every worker. 2340 func renterworkersmaintenancecmd() { 2341 rw, err := httpClient.RenterWorkersSortedGet(true) 2342 if err != nil { 2343 die("Could not get workers:", err) 2344 } 2345 2346 // Create tab writer 2347 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 2348 defer func() { 2349 err := w.Flush() 2350 if err != nil { 2351 die("Could not flush tabwriter:", err) 2352 } 2353 }() 2354 2355 // Write Maintenance Info 2356 writeWorkerMaintenanceInfo(w, rw) 2357 } 2358 2359 // renterworkersptcmd is the handler for the command `skyc renter workers pt`. 2360 // It lists the status of the price table of every worker. 2361 func renterworkersptcmd() { 2362 rw, err := httpClient.RenterWorkersSortedGet(true) 2363 if err != nil { 2364 die("Could not get workers:", err) 2365 } 2366 2367 // collect some overal account stats 2368 var workersWithoutPTs uint64 2369 for _, worker := range rw.Workers { 2370 if !worker.PriceTableStatus.Active { 2371 workersWithoutPTs++ 2372 } 2373 } 2374 fmt.Println("Worker Price Tables Summary") 2375 2376 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 2377 defer func() { 2378 err := w.Flush() 2379 if err != nil { 2380 die("Could not flush tabwriter:", err) 2381 } 2382 }() 2383 2384 // print summary 2385 fmt.Fprintf(w, "Total Workers: \t%v\n", rw.NumWorkers) 2386 fmt.Fprintf(w, "Workers Without Price Table: \t%v\n", workersWithoutPTs) 2387 2388 // print header 2389 hostInfo := "Host PubKey" 2390 priceTableInfo := "\tActive\tExpiry\tUpdate" 2391 queueInfo := "\tErrorAt\tError" 2392 header := hostInfo + priceTableInfo + queueInfo 2393 fmt.Fprintln(w, "\nWorker Price Tables Detail \n\n"+header) 2394 2395 // print rows 2396 for _, worker := range rw.Workers { 2397 pts := worker.PriceTableStatus 2398 2399 // Host Info 2400 fmt.Fprintf(w, "%v", worker.HostPubKey.String()) 2401 2402 // Price Table Info 2403 fmt.Fprintf(w, "\t%t\t%s\t%s", 2404 pts.Active, 2405 sanitizeTime(pts.ExpiryTime, pts.Active), 2406 sanitizeTime(pts.UpdateTime, pts.Active)) 2407 2408 // Error Info 2409 fmt.Fprintf(w, "\t%v\t%v\n", 2410 sanitizeTime(pts.RecentErrTime, pts.RecentErr != ""), 2411 sanitizeErr(pts.RecentErr)) 2412 } 2413 } 2414 2415 // renterworkersrjcmd is the handler for the command `skyc renter workers rj`. 2416 // It lists the status of the read job queue for every worker. 2417 func renterworkersrjcmd() { 2418 rw, err := httpClient.RenterWorkersSortedGet(true) 2419 if err != nil { 2420 die("Could not get workers:", err) 2421 } 2422 2423 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 2424 defer func() { 2425 err := w.Flush() 2426 if err != nil { 2427 die("Could not flush tabwriter:", err) 2428 } 2429 }() 2430 2431 // print summary 2432 fmt.Fprintf(w, "Worker Pool Summary \n") 2433 fmt.Fprintf(w, " Total Workers: \t%v\n", rw.NumWorkers) 2434 fmt.Fprintf(w, " Workers On Download Cooldown:\t%v\n", rw.TotalDownloadCoolDown) 2435 2436 // print header 2437 hostInfo := "Host PubKey" 2438 queueInfo := "\tJobs\tAvgJobTime64k (ms)\tAvgJobTime1m (ms)\tAvgJobTime4m (ms)\tConsecFail\tErrorAt\tError" 2439 cooldownInfo := "\tOn Cooldown\tCooldown Time" 2440 header := hostInfo + queueInfo + cooldownInfo 2441 fmt.Fprintln(w, "\nWorker Read Jobs \n\n"+header) 2442 2443 // print rows 2444 for _, worker := range rw.Workers { 2445 rjs := worker.ReadJobsStatus 2446 2447 // Host Info 2448 fmt.Fprintf(w, "%v", worker.HostPubKey.String()) 2449 2450 // ReadJobs Info 2451 fmt.Fprintf(w, "\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n", 2452 rjs.JobQueueSize, 2453 rjs.AvgJobTime64k, 2454 rjs.AvgJobTime1m, 2455 rjs.AvgJobTime4m, 2456 rjs.ConsecutiveFailures, 2457 sanitizeTime(rjs.RecentErrTime, rjs.RecentErr != ""), 2458 sanitizeErr(rjs.RecentErr), 2459 worker.DownloadOnCoolDown, 2460 absDuration(worker.DownloadCoolDownTime)) 2461 } 2462 } 2463 2464 // renterworkershsjcmd is the handler for the command `skyc renter workers hsj`. 2465 // It lists the status of the has sector job queue for every worker. 2466 func renterworkershsjcmd() { 2467 rw, err := httpClient.RenterWorkersSortedGet(true) 2468 if err != nil { 2469 die("Could not get workers:", err) 2470 } 2471 2472 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 2473 defer func() { 2474 err := w.Flush() 2475 if err != nil { 2476 die("Could not flush tabwriter:", err) 2477 } 2478 }() 2479 2480 // print summary 2481 fmt.Fprintf(w, "Worker Pool Summary \n") 2482 fmt.Fprintf(w, " Total Workers: \t%v\n", rw.NumWorkers) 2483 fmt.Fprintf(w, " Workers On HasSector Cooldown:\t%v\n", rw.TotalHasSectorCoolDown) 2484 2485 // print header 2486 hostInfo := "Host PubKey" 2487 queueInfo := "\tJobs\tAvgJobTime (ms)\tConsecFail\tErrorAt\tError" 2488 cooldownInfo := "\tOn Cooldown\tCooldown Time" 2489 header := hostInfo + queueInfo + cooldownInfo 2490 fmt.Fprintln(w, "\nWorker Has Sector Jobs \n\n"+header) 2491 2492 // print rows 2493 for _, worker := range rw.Workers { 2494 hsjs := worker.HasSectorJobsStatus 2495 2496 // Host Info 2497 fmt.Fprintf(w, "%v", worker.HostPubKey.String()) 2498 2499 // HasSector Jobs Info 2500 fmt.Fprintf(w, "\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n", 2501 hsjs.JobQueueSize, 2502 hsjs.AvgJobTime, 2503 hsjs.ConsecutiveFailures, 2504 sanitizeTime(hsjs.RecentErrTime, hsjs.RecentErr != ""), 2505 sanitizeErr(hsjs.RecentErr), 2506 worker.HasSectorOnCoolDown, 2507 absDuration(worker.HasSectorCoolDownTime)) 2508 } 2509 } 2510 2511 // renterworkersuploadscmd is the handler for the command `skyc renter workers 2512 // uj`. It lists the status of the upload jobs of every worker. 2513 func renterworkersuploadscmd() { 2514 rw, err := httpClient.RenterWorkersSortedGet(true) 2515 if err != nil { 2516 die("Could not get workers:", err) 2517 } 2518 2519 // Create tab writer 2520 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 2521 defer func() { 2522 err := w.Flush() 2523 if err != nil { 2524 die("Could not flush tabwriter:", err) 2525 } 2526 }() 2527 // Write Upload Info 2528 writeWorkerUploadInfo(w, rw) 2529 } 2530 2531 // writeWorkers is a helper function to display workers 2532 func writeWorkers(workers []skymodules.WorkerStatus) { 2533 fmt.Println(" Number of Workers:", len(workers)) 2534 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 2535 contractHeader := "Worker Contract\t \t \t " 2536 contractInfo := "Host PubKey\tContract ID\tGood For Renew\tGood For Upload" 2537 downloadHeader := "\tWorker Downloads\t " 2538 downloadInfo := "\tOn Cooldown\tQueue" 2539 uploadHeader := "\tWorker Uploads\t " 2540 uploadInfo := "\tOn Cooldown\tQueue" 2541 maintenanceHeader := "\tWorker Maintenance\t \t " 2542 maintenanceInfo := "\tOn Cooldown\tCooldown Time\tLast Error" 2543 jobHeader := "\tWorker Jobs\t \t " 2544 jobInfo := "\tHas Sector\tRead Sector\tSnapshot UL\tSnapshot DL" 2545 fmt.Fprintln(w, "\n "+contractHeader+downloadHeader+uploadHeader+maintenanceHeader+jobHeader) 2546 fmt.Fprintln(w, " "+contractInfo+downloadInfo+uploadInfo+maintenanceInfo+jobInfo) 2547 2548 for _, worker := range workers { 2549 // Contract Info 2550 fmt.Fprintf(w, " %v\t%v\t%v\t%v", 2551 worker.HostPubKey.String(), 2552 worker.ContractID, 2553 worker.ContractUtility.GoodForRenew, 2554 worker.ContractUtility.GoodForUpload) 2555 2556 // Download Info 2557 fmt.Fprintf(w, "\t%v\t%v", 2558 worker.DownloadOnCoolDown, 2559 worker.DownloadQueueSize) 2560 2561 // Upload Info 2562 fmt.Fprintf(w, "\t%v\t%v", 2563 worker.UploadOnCoolDown, 2564 worker.UploadQueueSize) 2565 2566 // Maintenance Info 2567 fmt.Fprintf(w, "\t%t\t%v\t%v", 2568 worker.MaintenanceOnCooldown, 2569 worker.MaintenanceCoolDownTime, 2570 sanitizeErr(worker.MaintenanceCoolDownError)) 2571 2572 // Job Info 2573 fmt.Fprintf(w, "\t%v\t%v\t%v\t%v\n", 2574 worker.HasSectorJobsStatus.JobQueueSize, 2575 worker.ReadJobsStatus.JobQueueSize, 2576 worker.DownloadSnapshotJobQueueSize, 2577 worker.UploadSnapshotJobQueueSize) 2578 } 2579 if err := w.Flush(); err != nil { 2580 die("failed to flush writer:", err) 2581 } 2582 } 2583 2584 // renterworkerreadregistrycmd is the handler for the command `skyc renter workers 2585 // rrj`. It lists the status of the read registry jobs of every worker. 2586 func renterworkersreadregistrycmd() { 2587 rw, err := httpClient.RenterWorkersSortedGet(true) 2588 if err != nil { 2589 die("Could not get workers:", err) 2590 } 2591 2592 // Create tab writer 2593 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 2594 defer func() { 2595 err := w.Flush() 2596 if err != nil { 2597 die("Could not flush tabwriter:", err) 2598 } 2599 }() 2600 // Write Registry Info 2601 writeWorkerReadUpdateRegistryInfo(true, w, rw) 2602 } 2603 2604 // renterworkerupdateregistrycmd is the handler for the command `skyc renter 2605 // workers urj`. It lists the status of the update registry jobs of every 2606 // worker. 2607 func renterworkersupdateregistrycmd() { 2608 rw, err := httpClient.RenterWorkersSortedGet(true) 2609 if err != nil { 2610 die("Could not get workers:", err) 2611 } 2612 2613 // Create tab writer 2614 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 2615 defer func() { 2616 err := w.Flush() 2617 if err != nil { 2618 die("Could not flush tabwriter:", err) 2619 } 2620 }() 2621 // Write Registry Info 2622 writeWorkerReadUpdateRegistryInfo(false, w, rw) 2623 } 2624 2625 // rentercontractinfoscancmd can be used to analyse a file which was previously 2626 // created by appending the result of rentercontractinfocmd multiple times. 2627 func rentercontractinfoscancmd(path string) { 2628 contracts := make(map[string]activeContractInfo) 2629 from := int64(math.MaxInt64) 2630 to := int64(0) 2631 scanFunc := func(ci contractsInfo) { 2632 if ci.Time < from { 2633 from = ci.Time 2634 } 2635 if ci.Time > to { 2636 to = ci.Time 2637 } 2638 2639 for _, contract := range ci.Contracts { 2640 oldInfo, exists := contracts[contract.ContractID] 2641 if !exists { 2642 contracts[contract.ContractID] = activeContractInfo{ 2643 contractInfo: contract, 2644 changed: []int64{ci.Time}, 2645 } 2646 continue 2647 } 2648 if contract.State != oldInfo.State { 2649 oldInfo.changed = append(oldInfo.changed, ci.Time) 2650 } 2651 oldInfo.contractInfo = contract 2652 contracts[contract.ContractID] = oldInfo 2653 } 2654 } 2655 scanUtilsFile(path, scanFunc) 2656 2657 // Sort the contract before printing them. They should be sorted by 2658 // state and contract id. 2659 sortedContracts := make([]activeContractInfo, 0, len(contracts)) 2660 for _, c := range contracts { 2661 sortedContracts = append(sortedContracts, c) 2662 } 2663 sort.Slice(sortedContracts, func(i, j int) bool { 2664 if sortedContracts[i].State == sortedContracts[j].State { 2665 return strings.Compare(sortedContracts[i].ContractID, sortedContracts[j].ContractID) < 0 2666 } else if sortedContracts[i].State == "active" { 2667 return true // i wins 2668 } else if sortedContracts[j].State == "active" { 2669 return false // j wins 2670 } else if sortedContracts[i].State == "disabled" { 2671 return false // j wins 2672 } else if sortedContracts[j].State == "disabled" { 2673 return true // i wins 2674 } 2675 die("unknown case", sortedContracts[i].State, sortedContracts[j].State) 2676 return false 2677 }) 2678 2679 fromT := time.Unix(from, 0) 2680 toT := time.Unix(to, 0) 2681 2682 fmt.Printf("Time Range: from %v to %v\n", fromT, toT) 2683 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 2684 for _, contract := range sortedContracts { 2685 fmt.Fprintf(w, "Contract: %v \t Host: %v \t HostFound: %v \t Malicious: %v \t Filtered: %v \t ContractSize: %v \t HostRemainingStorage: %v \t NumStateChanges: %v \t State: %v\n", contract.ContractID, contract.HostKey, contract.HostFound, contract.HostMalicious, contract.HostFiltered, modules.FilesizeUnits(contract.Size), modules.FilesizeUnits(contract.HostRemainingStorage), len(contract.changed), contract.State) 2686 } 2687 w.Flush() 2688 } 2689 2690 // rentercontractinfocmd is the handler for displaying the renter's contract 2691 // set's info in a compact single-line json encoding for dumping to a file. 2692 func rentercontractinfocmd() { 2693 rf, err := httpClient.RenterDirRootGet(skymodules.RootSiaPath()) 2694 if err != nil { 2695 die("Failed to get root dir info", err) 2696 } 2697 hosts, err := httpClient.HostDbAllGet() 2698 if err != nil { 2699 die("Failed to get host info", err) 2700 } 2701 hostMap := make(map[string]api.ExtendedHostDBEntry) 2702 for _, h := range hosts.Hosts { 2703 hostMap[h.PublicKeyString] = h 2704 } 2705 2706 rcg, err := httpClient.RenterAllContractsGet() 2707 if err != nil { 2708 die("Failed to fetch contracts from renter", err) 2709 } 2710 2711 stateMap := make(map[types.FileContractID]string) 2712 for _, c := range rcg.ActiveContracts { 2713 stateMap[c.ID] = "active" 2714 } 2715 for _, c := range rcg.PassiveContracts { 2716 stateMap[c.ID] = "passive" 2717 } 2718 for _, c := range rcg.DisabledContracts { 2719 stateMap[c.ID] = "disabled" 2720 } 2721 2722 sort.Slice(rcg.Contracts, func(i, j int) bool { 2723 return strings.Compare(rcg.Contracts[i].HostPublicKey.String(), rcg.Contracts[j].HostPublicKey.String()) < 0 2724 }) 2725 2726 ci := contractsInfo{ 2727 RepairData: rf.Directories[0].AggregateRepairSize, 2728 Time: time.Now().Unix(), 2729 } 2730 for _, c := range rcg.Contracts { 2731 state := stateMap[c.ID] 2732 info := contractInfo{ 2733 State: state, 2734 HostKey: c.HostPublicKey.String(), 2735 HostVersion: c.HostVersion, 2736 ContractID: c.ID.String(), 2737 GoodForRefresh: c.GoodForRefresh, 2738 GoodForRenew: c.GoodForRenew, 2739 GoodForUpload: c.GoodForUpload, 2740 BadContract: c.BadContract, 2741 Size: c.Size, 2742 } 2743 2744 host, found := hostMap[c.HostPublicKey.String()] 2745 if !found { 2746 ci.Contracts = append(ci.Contracts, info) 2747 continue 2748 } 2749 2750 info.HostFound = true 2751 info.HostOnline = len(host.ScanHistory) != 0 && host.ScanHistory[len(host.ScanHistory)-1].Success 2752 info.HostRemainingStorage = host.RemainingStorage 2753 info.HostMalicious = host.Malicious 2754 info.HostFiltered = host.Filtered 2755 ci.Contracts = append(ci.Contracts, info) 2756 } 2757 b, err := json.Marshal(ci) 2758 if err != nil { 2759 die("Failed to marshal contract info", err) 2760 } 2761 fmt.Println(string(b)) 2762 } 2763 2764 // scanUtilsFile is a helper to scan through a utils file and parse every line 2765 // of it. 2766 func scanUtilsFile(path string, scanFunc func(contractsInfo)) { 2767 f, err := os.Open(path) 2768 if err != nil { 2769 die("Failed to open file", err) 2770 } 2771 defer func() { 2772 if err := f.Close(); err != nil { 2773 die("Failed to close file", err) 2774 } 2775 }() 2776 2777 println(f.Name()) 2778 scanner := bufio.NewScanner(f) 2779 scanner.Buffer(make([]byte, 1<<20), 1<<20) 2780 scanner.Split(bufio.ScanLines) 2781 for scanner.Scan() { 2782 var ci contractsInfo 2783 if err := json.Unmarshal([]byte(scanner.Text()), &ci); err != nil { 2784 die("Failed to unmarshal line", err) 2785 } 2786 scanFunc(ci) 2787 } 2788 }