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  }