github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/cmd/siac/rentercmd.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"sort"
     8  	"strconv"
     9  	"text/tabwriter"
    10  	"time"
    11  
    12  	"github.com/spf13/cobra"
    13  
    14  	"github.com/Synthesix/Sia/modules"
    15  	"github.com/Synthesix/Sia/node/api"
    16  )
    17  
    18  var (
    19  	renterAllowanceCancelCmd = &cobra.Command{
    20  		Use:   "cancel",
    21  		Short: "Cancel the current allowance",
    22  		Long:  "Cancel the current allowance, which controls how much money is spent on file contracts.",
    23  		Run:   wrap(renterallowancecancelcmd),
    24  	}
    25  
    26  	renterAllowanceCmd = &cobra.Command{
    27  		Use:   "allowance",
    28  		Short: "View the current allowance",
    29  		Long:  "View the current allowance, which controls how much money is spent on file contracts.",
    30  		Run:   wrap(renterallowancecmd),
    31  	}
    32  
    33  	renterCmd = &cobra.Command{
    34  		Use:   "renter",
    35  		Short: "Perform renter actions",
    36  		Long:  "Upload, download, rename, delete, load, or share files.",
    37  		Run:   wrap(rentercmd),
    38  	}
    39  
    40  	renterContractsCmd = &cobra.Command{
    41  		Use:   "contracts",
    42  		Short: "View the Renter's contracts",
    43  		Long:  "View the contracts that the Renter has formed with hosts.",
    44  		Run:   wrap(rentercontractscmd),
    45  	}
    46  
    47  	renterContractsViewCmd = &cobra.Command{
    48  		Use:   "view [contract-id]",
    49  		Short: "View details of the specified contract",
    50  		Long:  "View all details available of the specified contract.",
    51  		Run:   wrap(rentercontractsviewcmd),
    52  	}
    53  
    54  	renterDownloadsCmd = &cobra.Command{
    55  		Use:   "downloads",
    56  		Short: "View the download queue",
    57  		Long:  "View the list of files currently downloading.",
    58  		Run:   wrap(renterdownloadscmd),
    59  	}
    60  
    61  	renterFilesDeleteCmd = &cobra.Command{
    62  		Use:     "delete [path]",
    63  		Aliases: []string{"rm"},
    64  		Short:   "Delete a file",
    65  		Long:    "Delete a file. Does not delete the file on disk.",
    66  		Run:     wrap(renterfilesdeletecmd),
    67  	}
    68  
    69  	renterFilesDownloadCmd = &cobra.Command{
    70  		Use:   "download [path] [destination]",
    71  		Short: "Download a file",
    72  		Long:  "Download a previously-uploaded file to a specified destination.",
    73  		Run:   wrap(renterfilesdownloadcmd),
    74  	}
    75  
    76  	renterFilesListCmd = &cobra.Command{
    77  		Use:     "list",
    78  		Aliases: []string{"ls"},
    79  		Short:   "List the status of all files",
    80  		Long:    "List the status of all files known to the renter on the Sia network.",
    81  		Run:     wrap(renterfileslistcmd),
    82  	}
    83  
    84  	renterFilesRenameCmd = &cobra.Command{
    85  		Use:     "rename [path] [newpath]",
    86  		Aliases: []string{"mv"},
    87  		Short:   "Rename a file",
    88  		Long:    "Rename a file.",
    89  		Run:     wrap(renterfilesrenamecmd),
    90  	}
    91  
    92  	renterFilesUploadCmd = &cobra.Command{
    93  		Use:   "upload [source] [path]",
    94  		Short: "Upload a file",
    95  		Long:  "Upload a file to [path] on the Sia network.",
    96  		Run:   wrap(renterfilesuploadcmd),
    97  	}
    98  
    99  	renterPricesCmd = &cobra.Command{
   100  		Use:   "prices",
   101  		Short: "Display the price of storage and bandwidth",
   102  		Long:  "Display the estimated prices of storing files, retrieving files, and creating a set of contracts",
   103  		Run:   wrap(renterpricescmd),
   104  	}
   105  
   106  	renterSetAllowanceCmd = &cobra.Command{
   107  		Use:   "setallowance [amount] [period] [hosts] [renew window]",
   108  		Short: "Set the allowance",
   109  		Long: `Set the amount of money that can be spent over a given period.
   110  
   111  amount is given in currency units (SC, KS, etc.)
   112  
   113  period is given in either blocks (b), hours (h), days (d), or weeks (w). A
   114  block is approximately 10 minutes, so one hour is six blocks, a day is 144
   115  blocks, and a week is 1008 blocks.
   116  
   117  The Sia renter module spreads data across more than one Sia server computer
   118  or "host". The "hosts" parameter for the setallowance command determines
   119  how many different hosts the renter will spread the data across.
   120  
   121  Allowance can be automatically renewed periodically. If the current
   122  blockheight + the renew window >= the end height the contract,
   123  then the contract is renewed automatically.
   124  
   125  Note that setting the allowance will cause siad to immediately begin forming
   126  contracts! You should only set the allowance once you are fully synced and you
   127  have a reasonable number (>30) of hosts in your hostdb.`,
   128  		Run: rentersetallowancecmd,
   129  	}
   130  
   131  	renterUploadsCmd = &cobra.Command{
   132  		Use:   "uploads",
   133  		Short: "View the upload queue",
   134  		Long:  "View the list of files currently uploading.",
   135  		Run:   wrap(renteruploadscmd),
   136  	}
   137  )
   138  
   139  // abs returns the absolute representation of a path.
   140  // TODO: bad things can happen if you run siac from a non-existent directory.
   141  // Implement some checks to catch this problem.
   142  func abs(path string) string {
   143  	abspath, err := filepath.Abs(path)
   144  	if err != nil {
   145  		return path
   146  	}
   147  	return abspath
   148  }
   149  
   150  // rentercmd displays the renter's financial metrics and lists the files it is
   151  // tracking.
   152  func rentercmd() {
   153  	var rg api.RenterGET
   154  	err := getAPI("/renter", &rg)
   155  	if err != nil {
   156  		die("Could not get renter info:", err)
   157  	}
   158  	fm := rg.FinancialMetrics
   159  	fmt.Printf(`Renter info:
   160  	Storage Spending:  %v
   161  	Upload Spending:   %v
   162  	Download Spending: %v
   163  	Unspent Funds:     %v
   164  	Total Allocated:   %v
   165  
   166  `, currencyUnits(fm.StorageSpending), currencyUnits(fm.UploadSpending),
   167  		currencyUnits(fm.DownloadSpending), currencyUnits(fm.Unspent),
   168  		currencyUnits(fm.ContractSpending))
   169  
   170  	// also list files
   171  	renterfileslistcmd()
   172  }
   173  
   174  // renteruploadscmd is the handler for the command `siac renter uploads`.
   175  // Lists files currently uploading.
   176  func renteruploadscmd() {
   177  	var rf api.RenterFiles
   178  	err := getAPI("/renter/files", &rf)
   179  	if err != nil {
   180  		die("Could not get upload queue:", err)
   181  	}
   182  
   183  	// TODO: add a --history flag to the uploads command to mirror the --history
   184  	//       flag in the downloads command. This hasn't been done yet because the
   185  	//       call to /renter/files includes files that have been shared with you,
   186  	//       not just files you've uploaded.
   187  
   188  	// Filter out files that have been uploaded.
   189  	var filteredFiles []modules.FileInfo
   190  	for _, fi := range rf.Files {
   191  		if !fi.Available {
   192  			filteredFiles = append(filteredFiles, fi)
   193  		}
   194  	}
   195  	if len(filteredFiles) == 0 {
   196  		fmt.Println("No files are uploading.")
   197  		return
   198  	}
   199  	fmt.Println("Uploading", len(filteredFiles), "files:")
   200  	for _, file := range filteredFiles {
   201  		fmt.Printf("%13s  %s (uploading, %0.2f%%)\n", filesizeUnits(int64(file.Filesize)), file.SiaPath, file.UploadProgress)
   202  	}
   203  }
   204  
   205  // renterdownloadscmd is the handler for the command `siac renter downloads`.
   206  // Lists files currently downloading, and optionally previously downloaded
   207  // files if the -H or --history flag is specified.
   208  func renterdownloadscmd() {
   209  	var queue api.RenterDownloadQueue
   210  	err := getAPI("/renter/downloads", &queue)
   211  	if err != nil {
   212  		die("Could not get download queue:", err)
   213  	}
   214  	// Filter out files that have been downloaded.
   215  	var downloading []api.DownloadInfo
   216  	for _, file := range queue.Downloads {
   217  		if file.Received != file.Filesize {
   218  			downloading = append(downloading, file)
   219  		}
   220  	}
   221  	if len(downloading) == 0 {
   222  		fmt.Println("No files are downloading.")
   223  	} else {
   224  		fmt.Println("Downloading", len(downloading), "files:")
   225  		for _, file := range downloading {
   226  			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)
   227  		}
   228  	}
   229  	if !renterShowHistory {
   230  		return
   231  	}
   232  	fmt.Println()
   233  	// Filter out files that are downloading.
   234  	var downloaded []api.DownloadInfo
   235  	for _, file := range queue.Downloads {
   236  		if file.Received == file.Filesize {
   237  			downloaded = append(downloaded, file)
   238  		}
   239  	}
   240  	if len(downloaded) == 0 {
   241  		fmt.Println("No files downloaded.")
   242  	} else {
   243  		fmt.Println("Downloaded", len(downloaded), "files:")
   244  		for _, file := range downloaded {
   245  			fmt.Printf("%s: %s -> %s\n", file.StartTime.Format("Jan 02 03:04 PM"), file.SiaPath, file.Destination)
   246  		}
   247  	}
   248  }
   249  
   250  // renterallowancecmd displays the current allowance.
   251  func renterallowancecmd() {
   252  	var rg api.RenterGET
   253  	err := getAPI("/renter", &rg)
   254  	if err != nil {
   255  		die("Could not get allowance:", err)
   256  	}
   257  	allowance := rg.Settings.Allowance
   258  
   259  	// convert to SC
   260  	fmt.Printf(`Allowance:
   261  	Amount: %v
   262  	Period: %v blocks
   263  `, currencyUnits(allowance.Funds), allowance.Period)
   264  }
   265  
   266  // renterallowancecancelcmd cancels the current allowance.
   267  func renterallowancecancelcmd() {
   268  	err := post("/renter", "hosts=0&funds=0&period=0&renewwindow=0")
   269  	if err != nil {
   270  		die("error canceling allowance:", err)
   271  	}
   272  	fmt.Println("Allowance canceled.")
   273  }
   274  
   275  // rentersetallowancecmd allows the user to set the allowance.
   276  // the first two parameters, amount and period, are required.
   277  // the second two parameters are optional:
   278  //    hosts                 integer number of hosts
   279  //    renewperiod           how many blocks between renewals
   280  func rentersetallowancecmd(cmd *cobra.Command, args []string) {
   281  	if len(args) < 2 || len(args) > 4 {
   282  		cmd.UsageFunc()(cmd)
   283  		os.Exit(exitCodeUsage)
   284  	}
   285  	hastings, err := parseCurrency(args[0])
   286  	if err != nil {
   287  		die("Could not parse amount:", err)
   288  	}
   289  	blocks, err := parsePeriod(args[1])
   290  	if err != nil {
   291  		die("Could not parse period")
   292  	}
   293  	queryString := fmt.Sprintf("funds=%s&period=%s", hastings, blocks)
   294  	if len(args) > 2 {
   295  		_, err = strconv.Atoi(args[2])
   296  		if err != nil {
   297  			die("Could not parse host count")
   298  		}
   299  		queryString += fmt.Sprintf("&hosts=%s", args[2])
   300  	}
   301  	if len(args) > 3 {
   302  		renewWindow, err := parsePeriod(args[3])
   303  		if err != nil {
   304  			die("Could not parse renew window")
   305  		}
   306  		queryString += fmt.Sprintf("&renewwindow=%s", renewWindow)
   307  	}
   308  	err = post("/renter", queryString)
   309  	if err != nil {
   310  		die("Could not set allowance:", err)
   311  	}
   312  	fmt.Println("Allowance updated.")
   313  }
   314  
   315  // byValue sorts contracts by their value in siacoins, high to low. If two
   316  // contracts have the same value, they are sorted by their host's address.
   317  type byValue []api.RenterContract
   318  
   319  func (s byValue) Len() int      { return len(s) }
   320  func (s byValue) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
   321  func (s byValue) Less(i, j int) bool {
   322  	cmp := s[i].RenterFunds.Cmp(s[j].RenterFunds)
   323  	if cmp == 0 {
   324  		return s[i].NetAddress < s[j].NetAddress
   325  	}
   326  	return cmp > 0
   327  }
   328  
   329  // rentercontractscmd is the handler for the comand `siac renter contracts`.
   330  // It lists the Renter's contracts.
   331  func rentercontractscmd() {
   332  	var rc api.RenterContracts
   333  	err := getAPI("/renter/contracts", &rc)
   334  	if err != nil {
   335  		die("Could not get contracts:", err)
   336  	}
   337  	if len(rc.Contracts) == 0 {
   338  		fmt.Println("No contracts have been formed.")
   339  		return
   340  	}
   341  	sort.Sort(byValue(rc.Contracts))
   342  	fmt.Println("Contracts:")
   343  	w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0)
   344  	fmt.Fprintln(w, "Host\tRemaining Funds\tSpent Funds\tSpent Fees\tData\tEnd Height\tID\tGoodForUpload\tGoodForRenew")
   345  	for _, c := range rc.Contracts {
   346  		fmt.Fprintf(w, "%v\t%8s\t%8s\t%8s\t%v\t%v\t%v\t%v\t%v\n",
   347  			c.NetAddress,
   348  			currencyUnits(c.RenterFunds),
   349  			currencyUnits(c.TotalCost.Sub(c.RenterFunds).Sub(c.Fees)),
   350  			currencyUnits(c.Fees),
   351  			filesizeUnits(int64(c.Size)),
   352  			c.EndHeight,
   353  			c.ID,
   354  			c.GoodForUpload,
   355  			c.GoodForRenew)
   356  	}
   357  	w.Flush()
   358  }
   359  
   360  // rentercontractsviewcmd is the handler for the command `siac renter contracts <id>`.
   361  // It lists details of a specific contract.
   362  func rentercontractsviewcmd(cid string) {
   363  	var rc api.RenterContracts
   364  	err := getAPI("/renter/contracts", &rc)
   365  	if err != nil {
   366  		die("Could not get contract details: ", err)
   367  	}
   368  
   369  	for _, rc := range rc.Contracts {
   370  		if rc.ID.String() == cid {
   371  			var hostInfo api.HostdbHostsGET
   372  			err = getAPI("/hostdb/hosts/"+rc.HostPublicKey.String(), &hostInfo)
   373  			if err != nil {
   374  				die("Could not fetch details of host: ", err)
   375  			}
   376  			fmt.Printf(`
   377  Contract %v
   378    Host: %v (Public Key: %v)
   379  
   380    Start Height: %v
   381    End Height:   %v
   382  
   383    Total cost:        %v (Fees: %v)
   384    Funds Allocated:   %v
   385    Upload Spending:   %v
   386    Storage Spending:  %v
   387    Download Spending: %v
   388    Remaining Funds:   %v
   389  
   390    File Size: %v
   391  `, rc.ID, rc.NetAddress, rc.HostPublicKey.String(), rc.StartHeight, rc.EndHeight,
   392  				currencyUnits(rc.TotalCost),
   393  				currencyUnits(rc.Fees),
   394  				currencyUnits(rc.TotalCost.Sub(rc.Fees)),
   395  				currencyUnits(rc.UploadSpending),
   396  				currencyUnits(rc.StorageSpending),
   397  				currencyUnits(rc.DownloadSpending),
   398  				currencyUnits(rc.RenterFunds),
   399  				filesizeUnits(int64(rc.Size)))
   400  
   401  			printScoreBreakdown(&hostInfo)
   402  			return
   403  		}
   404  	}
   405  
   406  	fmt.Println("Contract not found")
   407  }
   408  
   409  // renterfilesdeletecmd is the handler for the command `siac renter delete [path]`.
   410  // Removes the specified path from the Sia network.
   411  func renterfilesdeletecmd(path string) {
   412  	err := post("/renter/delete/"+path, "")
   413  	if err != nil {
   414  		die("Could not delete file:", err)
   415  	}
   416  	fmt.Println("Deleted", path)
   417  }
   418  
   419  // renterfilesdownloadcmd is the handler for the comand `siac renter download [path] [destination]`.
   420  // Downloads a path from the Sia network to the local specified destination.
   421  func renterfilesdownloadcmd(path, destination string) {
   422  	destination = abs(destination)
   423  	done := make(chan struct{})
   424  	go downloadprogress(done, path)
   425  
   426  	err := get("/renter/download/" + path + "?destination=" + destination)
   427  	close(done)
   428  	if err != nil {
   429  		die("Could not download file:", err)
   430  	}
   431  	fmt.Printf("\nDownloaded '%s' to %s.\n", path, abs(destination))
   432  }
   433  
   434  func downloadprogress(done chan struct{}, siapath string) {
   435  	time.Sleep(time.Second) // give download time to initialize
   436  	for {
   437  		select {
   438  		case <-done:
   439  			return
   440  
   441  		case <-time.Tick(time.Second):
   442  			// get download progress of file
   443  			var queue api.RenterDownloadQueue
   444  			err := getAPI("/renter/downloads", &queue)
   445  			if err != nil {
   446  				continue // benign
   447  			}
   448  			var d api.DownloadInfo
   449  			for _, d = range queue.Downloads {
   450  				if d.SiaPath == siapath {
   451  					break
   452  				}
   453  			}
   454  			if d.Filesize == 0 {
   455  				continue // file hasn't appeared in queue yet
   456  			}
   457  			pct := 100 * float64(d.Received) / float64(d.Filesize)
   458  			elapsed := time.Since(d.StartTime)
   459  			elapsed -= elapsed % time.Second // round to nearest second
   460  			mbps := (float64(d.Received*8) / 1e6) / time.Since(d.StartTime).Seconds()
   461  			fmt.Printf("\rDownloading... %5.1f%% of %v, %v elapsed, %.2f Mbps    ", pct, filesizeUnits(int64(d.Filesize)), elapsed, mbps)
   462  		}
   463  	}
   464  
   465  }
   466  
   467  // bySiaPath implements sort.Interface for [] modules.FileInfo based on the
   468  // SiaPath field.
   469  type bySiaPath []modules.FileInfo
   470  
   471  func (s bySiaPath) Len() int           { return len(s) }
   472  func (s bySiaPath) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
   473  func (s bySiaPath) Less(i, j int) bool { return s[i].SiaPath < s[j].SiaPath }
   474  
   475  // renterfileslistcmd is the handler for the command `siac renter list`.
   476  // Lists files known to the renter on the network.
   477  func renterfileslistcmd() {
   478  	var rf api.RenterFiles
   479  	err := getAPI("/renter/files", &rf)
   480  	if err != nil {
   481  		die("Could not get file list:", err)
   482  	}
   483  	if len(rf.Files) == 0 {
   484  		fmt.Println("No files have been uploaded.")
   485  		return
   486  	}
   487  	fmt.Println("Tracking", len(rf.Files), "files:")
   488  	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
   489  	if renterListVerbose {
   490  		fmt.Fprintln(w, "File size\tAvailable\tUploaded\tProgress\tRedundancy\tRenewing\tSia path")
   491  	}
   492  	sort.Sort(bySiaPath(rf.Files))
   493  	for _, file := range rf.Files {
   494  		fmt.Fprintf(w, "%9s", filesizeUnits(int64(file.Filesize)))
   495  		if renterListVerbose {
   496  			availableStr := yesNo(file.Available)
   497  			renewingStr := yesNo(file.Renewing)
   498  			redundancyStr := fmt.Sprintf("%.2f", file.Redundancy)
   499  			if file.Redundancy == -1 {
   500  				redundancyStr = "-"
   501  			}
   502  			uploadProgressStr := fmt.Sprintf("%.2f%%", file.UploadProgress)
   503  			if file.UploadProgress == -1 {
   504  				uploadProgressStr = "-"
   505  			}
   506  			fmt.Fprintf(w, "\t%s\t%9s\t%8s\t%10s\t%s", availableStr, filesizeUnits(int64(file.UploadedBytes)), uploadProgressStr, redundancyStr, renewingStr)
   507  		}
   508  		fmt.Fprintf(w, "\t%s", file.SiaPath)
   509  		if !renterListVerbose && !file.Available {
   510  			fmt.Fprintf(w, " (uploading, %0.2f%%)", file.UploadProgress)
   511  		}
   512  		fmt.Fprintln(w, "")
   513  	}
   514  	w.Flush()
   515  }
   516  
   517  // renterfilesrenamecmd is the handler for the command `siac renter rename [path] [newpath]`.
   518  // Renames a file on the Sia network.
   519  func renterfilesrenamecmd(path, newpath string) {
   520  	err := post("/renter/rename/"+path, "newsiapath="+newpath)
   521  	if err != nil {
   522  		die("Could not rename file:", err)
   523  	}
   524  	fmt.Printf("Renamed %s to %s\n", path, newpath)
   525  }
   526  
   527  // renterfilesuploadcmd is the handler for the command `siac renter upload
   528  // [source] [path]`. Uploads the [source] file to [path] on the Sia network.
   529  // If [source] is a directory, all files inside it will be uploaded and named
   530  // relative to [path].
   531  func renterfilesuploadcmd(source, path string) {
   532  	stat, err := os.Stat(source)
   533  	if err != nil {
   534  		die("Could not stat file or folder:", err)
   535  	}
   536  
   537  	if stat.IsDir() {
   538  		// folder
   539  		var files []string
   540  		err := filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
   541  			if err != nil {
   542  				fmt.Println("Warning: skipping file:", err)
   543  				return nil
   544  			}
   545  			if info.IsDir() {
   546  				return nil
   547  			}
   548  			files = append(files, path)
   549  			return nil
   550  		})
   551  		if err != nil {
   552  			die("Could not read folder:", err)
   553  		} else if len(files) == 0 {
   554  			die("Nothing to upload.")
   555  		}
   556  		for _, file := range files {
   557  			fpath, _ := filepath.Rel(source, file)
   558  			fpath = filepath.Join(path, fpath)
   559  			fpath = filepath.ToSlash(fpath)
   560  			err = post("/renter/upload/"+fpath, "source="+abs(file))
   561  			if err != nil {
   562  				die("Could not upload file:", err)
   563  			}
   564  		}
   565  		fmt.Printf("Uploaded %d files into '%s'.\n", len(files), path)
   566  	} else {
   567  		// single file
   568  		err = post("/renter/upload/"+path, "source="+abs(source))
   569  		if err != nil {
   570  			die("Could not upload file:", err)
   571  		}
   572  		fmt.Printf("Uploaded '%s' as %s.\n", abs(source), path)
   573  	}
   574  }
   575  
   576  // renterpricescmd is the handler for the command `siac renter prices`, which
   577  // displays the prices of various storage operations.
   578  func renterpricescmd() {
   579  	var rpg api.RenterPricesGET
   580  	err := getAPI("/renter/prices", &rpg)
   581  	if err != nil {
   582  		die("Could not read the renter prices:", err)
   583  	}
   584  
   585  	fmt.Println("Renter Prices (estimated):")
   586  	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
   587  	fmt.Fprintln(w, "\tFees for Creating a Set of Contracts:\t", currencyUnits(rpg.FormContracts))
   588  	fmt.Fprintln(w, "\tDownload 1 TB:\t", currencyUnits(rpg.DownloadTerabyte))
   589  	fmt.Fprintln(w, "\tStore 1 TB for 1 Month:\t", currencyUnits(rpg.StorageTerabyteMonth))
   590  	fmt.Fprintln(w, "\tUpload 1 TB:\t", currencyUnits(rpg.UploadTerabyte))
   591  	w.Flush()
   592  }