gitlab.com/SiaPrime/SiaPrime@v1.4.1/node/api/renter.go (about)

     1  package api
     2  
     3  import (
     4  	"encoding/hex"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"net/url"
     9  	"os"
    10  	"path/filepath"
    11  	"reflect"
    12  	"strconv"
    13  	"time"
    14  
    15  	"gitlab.com/SiaPrime/SiaPrime/modules/renter/contractor"
    16  
    17  	"github.com/julienschmidt/httprouter"
    18  	"gitlab.com/NebulousLabs/errors"
    19  	"gitlab.com/NebulousLabs/fastrand"
    20  
    21  	"gitlab.com/SiaPrime/SiaPrime/build"
    22  	"gitlab.com/SiaPrime/SiaPrime/crypto"
    23  	"gitlab.com/SiaPrime/SiaPrime/modules"
    24  	"gitlab.com/SiaPrime/SiaPrime/modules/renter/proto"
    25  	"gitlab.com/SiaPrime/SiaPrime/modules/renter/siafile"
    26  	"gitlab.com/SiaPrime/SiaPrime/types"
    27  )
    28  
    29  var (
    30  	// requiredHosts specifies the minimum number of hosts that must be set in
    31  	// the renter settings for the renter settings to be valid. This minimum is
    32  	// there to prevent users from shooting themselves in the foot.
    33  	requiredHosts = build.Select(build.Var{
    34  		Standard: uint64(20),
    35  		Dev:      uint64(1),
    36  		Testing:  uint64(1),
    37  	}).(uint64)
    38  
    39  	// requiredParityPieces specifies the minimum number of parity pieces that
    40  	// must be used when uploading a file. This minimum exists to prevent users
    41  	// from shooting themselves in the foot.
    42  	requiredParityPieces = build.Select(build.Var{
    43  		Standard: int(12),
    44  		Dev:      int(0),
    45  		Testing:  int(0),
    46  	}).(int)
    47  
    48  	// requiredRedundancy specifies the minimum redundancy that will be
    49  	// accepted by the renter when uploading a file. This minimum exists to
    50  	// prevent users from shooting themselves in the foot.
    51  	requiredRedundancy = build.Select(build.Var{
    52  		Standard: float64(2),
    53  		Dev:      float64(1),
    54  		Testing:  float64(1),
    55  	}).(float64)
    56  
    57  	// requiredRenewWindow establishes the minimum allowed renew window for the
    58  	// renter settings. This minimum is here to prevent users from shooting
    59  	// themselves in the foot.
    60  	requiredRenewWindow = build.Select(build.Var{
    61  		Standard: types.BlockHeight(288),
    62  		Dev:      types.BlockHeight(1),
    63  		Testing:  types.BlockHeight(1),
    64  	}).(types.BlockHeight)
    65  
    66  	//BackupKeySpecifier is the specifier used for deriving the secret used to
    67  	//encrypt a backup from the RenterSeed.
    68  	backupKeySpecifier = types.Specifier{'b', 'a', 'c', 'k', 'u', 'p', 'k', 'e', 'y'}
    69  
    70  	// errNeedBothDataAndParityPieces is the error returned when only one of the
    71  	// erasure coding parameters is set
    72  	errNeedBothDataAndParityPieces = errors.New("must provide both the datapieces parameter and the paritypieces parameter if specifying erasure coding parameters")
    73  )
    74  
    75  type (
    76  	// RenterGET contains various renter metrics.
    77  	RenterGET struct {
    78  		Settings         modules.RenterSettings     `json:"settings"`
    79  		FinancialMetrics modules.ContractorSpending `json:"financialmetrics"`
    80  		CurrentPeriod    types.BlockHeight          `json:"currentperiod"`
    81  	}
    82  
    83  	// RenterContract represents a contract formed by the renter.
    84  	RenterContract struct {
    85  		// Amount of contract funds that have been spent on downloads.
    86  		DownloadSpending types.Currency `json:"downloadspending"`
    87  		// Block height that the file contract ends on.
    88  		EndHeight types.BlockHeight `json:"endheight"`
    89  		// Fees paid in order to form the file contract.
    90  		Fees types.Currency `json:"fees"`
    91  		// Public key of the host the contract was formed with.
    92  		HostPublicKey types.SiaPublicKey `json:"hostpublickey"`
    93  		// HostVersion is the version of Sia that the host is running
    94  		HostVersion string `json:"hostversion"`
    95  		// ID of the file contract.
    96  		ID types.FileContractID `json:"id"`
    97  		// A signed transaction containing the most recent contract revision.
    98  		LastTransaction types.Transaction `json:"lasttransaction"`
    99  		// Address of the host the file contract was formed with.
   100  		NetAddress modules.NetAddress `json:"netaddress"`
   101  		// Remaining funds left for the renter to spend on uploads & downloads.
   102  		RenterFunds types.Currency `json:"renterfunds"`
   103  		// Size of the file contract, which is typically equal to the number of
   104  		// bytes that have been uploaded to the host.
   105  		Size uint64 `json:"size"`
   106  		// Block height that the file contract began on.
   107  		StartHeight types.BlockHeight `json:"startheight"`
   108  		// Amount of contract funds that have been spent on storage.
   109  		StorageSpending types.Currency `json:"storagespending"`
   110  		// DEPRECATED: This is the exact same value as StorageSpending, but it has
   111  		// incorrect capitalization. This was fixed in 1.3.2, but this field is kept
   112  		// to preserve backwards compatibility on clients who depend on the
   113  		// incorrect capitalization. This field will be removed in the future, so
   114  		// clients should switch to the StorageSpending field (above) with the
   115  		// correct lowercase name.
   116  		StorageSpendingDeprecated types.Currency `json:"StorageSpending"`
   117  		// Total cost to the wallet of forming the file contract.
   118  		TotalCost types.Currency `json:"totalcost"`
   119  		// Amount of contract funds that have been spent on uploads.
   120  		UploadSpending types.Currency `json:"uploadspending"`
   121  		// Signals if contract is good for uploading data
   122  		GoodForUpload bool `json:"goodforupload"`
   123  		// Signals if contract is good for a renewal
   124  		GoodForRenew bool `json:"goodforrenew"`
   125  	}
   126  
   127  	// RenterContracts contains the renter's contracts.
   128  	RenterContracts struct {
   129  		// Compatibility Fields
   130  		Contracts         []RenterContract `json:"contracts"`
   131  		InactiveContracts []RenterContract `json:"inactivecontracts"`
   132  
   133  		// Current Fields
   134  		ActiveContracts           []RenterContract              `json:"activecontracts"`
   135  		PassiveContracts          []RenterContract              `json:"passivecontracts"`
   136  		RefreshedContracts        []RenterContract              `json:"refreshedcontracts"`
   137  		DisabledContracts         []RenterContract              `json:"disabledcontracts"`
   138  		ExpiredContracts          []RenterContract              `json:"expiredcontracts"`
   139  		ExpiredRefreshedContracts []RenterContract              `json:"expiredrefreshedcontracts"`
   140  		RecoverableContracts      []modules.RecoverableContract `json:"recoverablecontracts"`
   141  	}
   142  
   143  	// RenterDirectory lists the files and directories contained in the queried
   144  	// directory
   145  	RenterDirectory struct {
   146  		Directories []modules.DirectoryInfo `json:"directories"`
   147  		Files       []modules.FileInfo      `json:"files"`
   148  	}
   149  
   150  	// RenterDownloadQueue contains the renter's download queue.
   151  	RenterDownloadQueue struct {
   152  		Downloads []DownloadInfo `json:"downloads"`
   153  	}
   154  
   155  	// RenterFile lists the file queried.
   156  	RenterFile struct {
   157  		File modules.FileInfo `json:"file"`
   158  	}
   159  
   160  	// RenterFiles lists the files known to the renter.
   161  	RenterFiles struct {
   162  		Files []modules.FileInfo `json:"files"`
   163  	}
   164  
   165  	// RenterLoad lists files that were loaded into the renter.
   166  	RenterLoad struct {
   167  		FilesAdded []string `json:"filesadded"`
   168  	}
   169  
   170  	// RenterPricesGET lists the data that is returned when a GET call is made
   171  	// to /renter/prices.
   172  	RenterPricesGET struct {
   173  		modules.RenterPriceEstimation
   174  		modules.Allowance
   175  	}
   176  	// RenterRecoveryStatusGET returns information about potential contract
   177  	// recovery scans.
   178  	RenterRecoveryStatusGET struct {
   179  		ScanInProgress bool              `json:"scaninprogress"`
   180  		ScannedHeight  types.BlockHeight `json:"scannedheight"`
   181  	}
   182  	// RenterShareASCII contains an ASCII-encoded .sia file.
   183  	RenterShareASCII struct {
   184  		ASCIIsia string `json:"asciisia"`
   185  	}
   186  
   187  	// RenterUploadedBackup describes an uploaded backup.
   188  	RenterUploadedBackup struct {
   189  		Name           string          `json:"name"`
   190  		CreationDate   types.Timestamp `json:"creationdate"`
   191  		Size           uint64          `json:"size"`
   192  		UploadProgress float64         `json:"uploadprogress"`
   193  	}
   194  
   195  	// RenterBackupsGET lists the renter's uploaded backups, as well as the
   196  	// set of contracts storing all known backups.
   197  	RenterBackupsGET struct {
   198  		Backups       []RenterUploadedBackup `json:"backups"`
   199  		SyncedHosts   []types.SiaPublicKey   `json:"syncedhosts"`
   200  		UnsyncedHosts []types.SiaPublicKey   `json:"unsyncedhosts"`
   201  	}
   202  
   203  	// RenterUploadReadyGet lists the upload ready status of the renter
   204  	RenterUploadReadyGet struct {
   205  		// Ready indicates whether of not the renter is ready to successfully
   206  		// upload to full redundancy based on the erasure coding provided and
   207  		// the number of contracts
   208  		Ready bool `json:"ready"`
   209  
   210  		// Contract information
   211  		ContractsNeeded    int `json:"contractsneeded"`
   212  		NumActiveContracts int `json:"numactivecontracts"`
   213  
   214  		// Erasure Coding information
   215  		DataPieces   int `json:"datapieces"`
   216  		ParityPieces int `json:"paritypieces"`
   217  	}
   218  
   219  	// DownloadInfo contains all client-facing information of a file.
   220  	DownloadInfo struct {
   221  		Destination     string          `json:"destination"`     // The destination of the download.
   222  		DestinationType string          `json:"destinationtype"` // Can be "file", "memory buffer", or "http stream".
   223  		Filesize        uint64          `json:"filesize"`        // DEPRECATED. Same as 'Length'.
   224  		Length          uint64          `json:"length"`          // The length requested for the download.
   225  		Offset          uint64          `json:"offset"`          // The offset within the siafile requested for the download.
   226  		SiaPath         modules.SiaPath `json:"siapath"`         // The siapath of the file used for the download.
   227  
   228  		Completed            bool      `json:"completed"`            // Whether or not the download has completed.
   229  		EndTime              time.Time `json:"endtime"`              // The time when the download fully completed.
   230  		Error                string    `json:"error"`                // Will be the empty string unless there was an error.
   231  		Received             uint64    `json:"received"`             // Amount of data confirmed and decoded.
   232  		StartTime            time.Time `json:"starttime"`            // The time when the download was started.
   233  		StartTimeUnix        int64     `json:"starttimeunix"`        // The time when the download was started in unix format.
   234  		TotalDataTransferred uint64    `json:"totaldatatransferred"` // The total amount of data transferred, including negotiation, overdrive etc.
   235  	}
   236  )
   237  
   238  // renterBackupsHandlerGET handles the API calls to /renter/backups.
   239  func (api *API) renterBackupsHandlerGET(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   240  	backups, syncedHosts, err := api.renter.UploadedBackups()
   241  	if err != nil {
   242  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
   243  		return
   244  	}
   245  	var unsyncedHosts []types.SiaPublicKey
   246  outer:
   247  	for _, c := range api.renter.Contracts() {
   248  		for _, h := range syncedHosts {
   249  			if c.HostPublicKey.String() == h.String() {
   250  				continue outer
   251  			}
   252  		}
   253  		unsyncedHosts = append(unsyncedHosts, c.HostPublicKey)
   254  	}
   255  
   256  	// if requested, fetch the backups stored on a specific host
   257  	if req.FormValue("host") != "" {
   258  		var hostKey types.SiaPublicKey
   259  		hostKey.LoadString(req.FormValue("host"))
   260  		if hostKey.Key == nil {
   261  			WriteError(w, Error{"invalid host public key"}, http.StatusBadRequest)
   262  			return
   263  		}
   264  		backups, err = api.renter.BackupsOnHost(hostKey)
   265  		if err != nil {
   266  			WriteError(w, Error{err.Error()}, http.StatusBadRequest)
   267  			return
   268  		}
   269  	}
   270  
   271  	rups := make([]RenterUploadedBackup, len(backups))
   272  	for i, b := range backups {
   273  		rups[i] = RenterUploadedBackup{
   274  			Name:           b.Name,
   275  			CreationDate:   b.CreationDate,
   276  			Size:           b.Size,
   277  			UploadProgress: b.UploadProgress,
   278  		}
   279  	}
   280  	WriteJSON(w, RenterBackupsGET{
   281  		Backups:       rups,
   282  		SyncedHosts:   syncedHosts,
   283  		UnsyncedHosts: unsyncedHosts,
   284  	})
   285  }
   286  
   287  // renterBackupsCreateHandlerPOST handles the API calls to /renter/backups/create
   288  func (api *API) renterBackupsCreateHandlerPOST(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   289  	// Check that a name was specified.
   290  	name := req.FormValue("name")
   291  	if name == "" {
   292  		WriteError(w, Error{"name not specified"}, http.StatusBadRequest)
   293  		return
   294  	}
   295  
   296  	// Write the backup to a temporary file and delete it after uploading.
   297  	tmpDir, err := ioutil.TempDir("", "sia-backup")
   298  	if err != nil {
   299  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
   300  		return
   301  	}
   302  	defer os.RemoveAll(tmpDir)
   303  	backupPath := filepath.Join(tmpDir, name)
   304  
   305  	// Get the wallet seed.
   306  	ws, _, err := api.wallet.PrimarySeed()
   307  	if err != nil {
   308  		WriteError(w, Error{"failed to get wallet's primary seed"}, http.StatusInternalServerError)
   309  		return
   310  	}
   311  	// Derive the renter seed and wipe the memory once we are done using it.
   312  	rs := proto.DeriveRenterSeed(ws)
   313  	defer fastrand.Read(rs[:])
   314  	// Derive the secret and wipe it afterwards.
   315  	secret := crypto.HashAll(rs, backupKeySpecifier)
   316  	defer fastrand.Read(secret[:])
   317  	// Create the backup.
   318  	if err := api.renter.CreateBackup(backupPath, secret[:32]); err != nil {
   319  		WriteError(w, Error{"failed to create backup: " + err.Error()}, http.StatusBadRequest)
   320  		return
   321  	}
   322  	// Upload the backup.
   323  	if err := api.renter.UploadBackup(backupPath, name); err != nil {
   324  		WriteError(w, Error{"failed to upload backup: " + err.Error()}, http.StatusBadRequest)
   325  		return
   326  	}
   327  	WriteSuccess(w)
   328  }
   329  
   330  // renterBackupsRestoreHandlerGET handles the API calls to /renter/backups/restore
   331  func (api *API) renterBackupsRestoreHandlerGET(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   332  	// Check that a name was specified.
   333  	name := req.FormValue("name")
   334  	if name == "" {
   335  		WriteError(w, Error{"name not specified"}, http.StatusBadRequest)
   336  		return
   337  	}
   338  	// Write the backup to a temporary file and delete it after loading.
   339  	tmpDir, err := ioutil.TempDir("", "sia-backup")
   340  	if err != nil {
   341  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
   342  		return
   343  	}
   344  	defer os.RemoveAll(tmpDir)
   345  	backupPath := filepath.Join(tmpDir, name)
   346  	if err := api.renter.DownloadBackup(backupPath, name); err != nil {
   347  		WriteError(w, Error{"failed to download backup: " + err.Error()}, http.StatusBadRequest)
   348  		return
   349  	}
   350  	// Get the wallet seed.
   351  	ws, _, err := api.wallet.PrimarySeed()
   352  	if err != nil {
   353  		WriteError(w, Error{"failed to get wallet's primary seed"}, http.StatusInternalServerError)
   354  		return
   355  	}
   356  	// Derive the renter seed and wipe the memory once we are done using it.
   357  	rs := proto.DeriveRenterSeed(ws)
   358  	defer fastrand.Read(rs[:])
   359  	// Derive the secret and wipe it afterwards.
   360  	secret := crypto.HashAll(rs, backupKeySpecifier)
   361  	defer fastrand.Read(secret[:])
   362  	// Load the backup.
   363  	if err := api.renter.LoadBackup(backupPath, secret[:32]); err != nil {
   364  		WriteError(w, Error{"failed to load backup: " + err.Error()}, http.StatusBadRequest)
   365  		return
   366  	}
   367  	WriteSuccess(w)
   368  }
   369  
   370  // renterBackupHandlerPOST handles the API calls to /renter/backup
   371  func (api *API) renterBackupHandlerPOST(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   372  	// Check that destination was specified.
   373  	dst := req.FormValue("destination")
   374  	if dst == "" {
   375  		WriteError(w, Error{"destination not specified"}, http.StatusBadRequest)
   376  		return
   377  	}
   378  	// The destination needs to be an absolute path.
   379  	if !filepath.IsAbs(dst) {
   380  		WriteError(w, Error{"destination must be an absolute path"}, http.StatusBadRequest)
   381  		return
   382  	}
   383  	// Get the wallet seed.
   384  	ws, _, err := api.wallet.PrimarySeed()
   385  	if err != nil {
   386  		WriteError(w, Error{"failed to get wallet's primary seed"}, http.StatusInternalServerError)
   387  		return
   388  	}
   389  	// Derive the renter seed and wipe the memory once we are done using it.
   390  	rs := proto.DeriveRenterSeed(ws)
   391  	defer fastrand.Read(rs[:])
   392  	// Derive the secret and wipe it afterwards.
   393  	secret := crypto.HashAll(rs, backupKeySpecifier)
   394  	defer fastrand.Read(secret[:])
   395  	// Create the backup.
   396  	if err := api.renter.CreateBackup(dst, secret[:32]); err != nil {
   397  		WriteError(w, Error{"failed to create backup: " + err.Error()}, http.StatusBadRequest)
   398  		return
   399  	}
   400  	WriteSuccess(w)
   401  }
   402  
   403  // renterBackupHandlerPOST handles the API calls to /renter/recoverbackup
   404  func (api *API) renterLoadBackupHandlerPOST(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   405  	// Check that source was specified.
   406  	src := req.FormValue("source")
   407  	if src == "" {
   408  		WriteError(w, Error{"source not specified"}, http.StatusBadRequest)
   409  		return
   410  	}
   411  	// The source needs to be an absolute path.
   412  	if !filepath.IsAbs(src) {
   413  		WriteError(w, Error{"source must be an absolute path"}, http.StatusBadRequest)
   414  		return
   415  	}
   416  	// Get the wallet seed.
   417  	ws, _, err := api.wallet.PrimarySeed()
   418  	if err != nil {
   419  		WriteError(w, Error{"failed to get wallet's primary seed"}, http.StatusInternalServerError)
   420  		return
   421  	}
   422  	// Derive the renter seed and wipe the memory once we are done using it.
   423  	rs := proto.DeriveRenterSeed(ws)
   424  	defer fastrand.Read(rs[:])
   425  	// Derive the secret and wipe it afterwards.
   426  	secret := crypto.HashAll(rs, backupKeySpecifier)
   427  	defer fastrand.Read(secret[:])
   428  	// Load the backup.
   429  	if err := api.renter.LoadBackup(src, secret[:32]); err != nil {
   430  		WriteError(w, Error{"failed to load backup: " + err.Error()}, http.StatusBadRequest)
   431  		return
   432  	}
   433  	WriteSuccess(w)
   434  }
   435  
   436  // parseErasureCodingParameters parses the supplied string values and creates
   437  // an erasure coder. If values haven't been supplied it will fill in sane
   438  // defaults.
   439  func parseErasureCodingParameters(strDataPieces, strParityPieces string) (modules.ErasureCoder, error) {
   440  	// Parse data and parity pieces
   441  	dataPieces, parityPieces, err := parseDataAndParityPieces(strDataPieces, strParityPieces)
   442  	if err != nil {
   443  		return nil, err
   444  	}
   445  
   446  	// Check if data and parity pieces were set
   447  	if dataPieces == 0 && parityPieces == 0 {
   448  		return nil, nil
   449  	}
   450  
   451  	// Verify that sane values for parityPieces and redundancy are being
   452  	// supplied.
   453  	if parityPieces < requiredParityPieces {
   454  		err := fmt.Errorf("a minimum of %v parity pieces is required, but %v parity pieces requested", parityPieces, requiredParityPieces)
   455  		return nil, err
   456  	}
   457  	redundancy := float64(dataPieces+parityPieces) / float64(dataPieces)
   458  	if float64(dataPieces+parityPieces)/float64(dataPieces) < requiredRedundancy {
   459  		err := fmt.Errorf("a redundancy of %.2f is required, but redundancy of %.2f supplied", redundancy, requiredRedundancy)
   460  		return nil, err
   461  	}
   462  
   463  	// Create the erasure coder.
   464  	return siafile.NewRSSubCode(dataPieces, parityPieces, crypto.SegmentSize)
   465  }
   466  
   467  // parseDataAndParityPieces parse the numeric values for dataPieces and
   468  // parityPieces from the input strings
   469  func parseDataAndParityPieces(strDataPieces, strParityPieces string) (dataPieces, parityPieces int, err error) {
   470  	// Check that both values have been supplied.
   471  	if (strDataPieces == "") != (strParityPieces == "") {
   472  		return 0, 0, errNeedBothDataAndParityPieces
   473  	}
   474  
   475  	// Check for blank strings.
   476  	if strDataPieces == "" && strParityPieces == "" {
   477  		return 0, 0, nil
   478  	}
   479  
   480  	// Parse dataPieces and Parity Pieces.
   481  	_, err = fmt.Sscan(strDataPieces, &dataPieces)
   482  	if err != nil {
   483  		err = errors.AddContext(err, "unable to read parameter 'datapieces'")
   484  		return 0, 0, err
   485  	}
   486  	_, err = fmt.Sscan(strParityPieces, &parityPieces)
   487  	if err != nil {
   488  		err = errors.AddContext(err, "unable to read parameter 'paritypieces'")
   489  		return 0, 0, err
   490  	}
   491  
   492  	// Check that either both values are zero or neither are zero
   493  	if (dataPieces == 0) != (parityPieces == 0) {
   494  		return 0, 0, errNeedBothDataAndParityPieces
   495  	}
   496  
   497  	return dataPieces, parityPieces, nil
   498  }
   499  
   500  // renterHandlerGET handles the API call to /renter.
   501  func (api *API) renterHandlerGET(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   502  	WriteJSON(w, RenterGET{
   503  		Settings:         api.renter.Settings(),
   504  		FinancialMetrics: api.renter.PeriodSpending(),
   505  		CurrentPeriod:    api.renter.CurrentPeriod(),
   506  	})
   507  }
   508  
   509  // renterHandlerPOST handles the API call to set the Renter's settings. This API
   510  // call handles multiple settings and so each setting is optional on it's own.
   511  // Groups of settings, such as the allowance, have certain requirements if they
   512  // are being set in which case certain fields are no longer optional.
   513  func (api *API) renterHandlerPOST(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   514  	// Get the existing settings
   515  	settings := api.renter.Settings()
   516  
   517  	// Scan for all allowance fields
   518  	if f := req.FormValue("funds"); f != "" {
   519  		funds, ok := scanAmount(f)
   520  		if !ok {
   521  			WriteError(w, Error{"unable to parse funds"}, http.StatusBadRequest)
   522  			return
   523  		}
   524  		settings.Allowance.Funds = funds
   525  	}
   526  	if h := req.FormValue("hosts"); h != "" {
   527  		var hosts uint64
   528  		if _, err := fmt.Sscan(h, &hosts); err != nil {
   529  			WriteError(w, Error{"unable to parse hosts: " + err.Error()}, http.StatusBadRequest)
   530  			return
   531  		} else if hosts != 0 && hosts < requiredHosts {
   532  			WriteError(w, Error{fmt.Sprintf("insufficient number of hosts, need at least %v but have %v", requiredHosts, hosts)}, http.StatusBadRequest)
   533  			return
   534  		}
   535  		settings.Allowance.Hosts = hosts
   536  	}
   537  	if p := req.FormValue("period"); p != "" {
   538  		var period types.BlockHeight
   539  		if _, err := fmt.Sscan(p, &period); err != nil {
   540  			WriteError(w, Error{"unable to parse period: " + err.Error()}, http.StatusBadRequest)
   541  			return
   542  		}
   543  		settings.Allowance.Period = types.BlockHeight(period)
   544  	}
   545  	if rw := req.FormValue("renewwindow"); rw != "" {
   546  		var renewWindow types.BlockHeight
   547  		if _, err := fmt.Sscan(rw, &renewWindow); err != nil {
   548  			WriteError(w, Error{"unable to parse renewwindow: " + err.Error()}, http.StatusBadRequest)
   549  			return
   550  		} else if renewWindow != 0 && types.BlockHeight(renewWindow) < requiredRenewWindow {
   551  			WriteError(w, Error{fmt.Sprintf("renew window is too small, must be at least %v blocks but have %v blocks", requiredRenewWindow, renewWindow)}, http.StatusBadRequest)
   552  			return
   553  		} else if renewWindow == 0 && settings.Allowance.Period != 0 {
   554  			WriteError(w, Error{contractor.ErrAllowanceZeroWindow.Error()}, http.StatusBadRequest)
   555  			return
   556  		} else {
   557  			settings.Allowance.RenewWindow = types.BlockHeight(renewWindow)
   558  		}
   559  	}
   560  	if es := req.FormValue("expectedstorage"); es != "" {
   561  		var expectedStorage uint64
   562  		if _, err := fmt.Sscan(es, &expectedStorage); err != nil {
   563  			WriteError(w, Error{"unable to parse expectedStorage: " + err.Error()}, http.StatusBadRequest)
   564  			return
   565  		}
   566  		settings.Allowance.ExpectedStorage = expectedStorage
   567  	}
   568  	if euf := req.FormValue("expectedupload"); euf != "" {
   569  		var expectedUpload uint64
   570  		if _, err := fmt.Sscan(euf, &expectedUpload); err != nil {
   571  			WriteError(w, Error{"unable to parse expectedUpload: " + err.Error()}, http.StatusBadRequest)
   572  			return
   573  		}
   574  		settings.Allowance.ExpectedUpload = expectedUpload
   575  	}
   576  	if edf := req.FormValue("expecteddownload"); edf != "" {
   577  		var expectedDownload uint64
   578  		if _, err := fmt.Sscan(edf, &expectedDownload); err != nil {
   579  			WriteError(w, Error{"unable to parse expectedDownload: " + err.Error()}, http.StatusBadRequest)
   580  			return
   581  		}
   582  		settings.Allowance.ExpectedDownload = expectedDownload
   583  	}
   584  	if er := req.FormValue("expectedredundancy"); er != "" {
   585  		var expectedRedundancy float64
   586  		if _, err := fmt.Sscan(er, &expectedRedundancy); err != nil {
   587  			WriteError(w, Error{"unable to parse expectedRedundancy: " + err.Error()}, http.StatusBadRequest)
   588  			return
   589  		}
   590  		settings.Allowance.ExpectedRedundancy = expectedRedundancy
   591  	}
   592  
   593  	// Validate any allowance changes
   594  	if !reflect.DeepEqual(settings.Allowance, modules.Allowance{}) {
   595  		// Allowance has been set at least partially. Validate that all fields
   596  		// are set correctly
   597  
   598  		// If Funds is still 0 return an error since we need the user to set the funding initially
   599  		if settings.Allowance.Funds.IsZero() {
   600  			WriteError(w, Error{"funds needs to be set if it hasn't been set before"}, http.StatusBadRequest)
   601  			return
   602  		}
   603  
   604  		// If Period is still 0 return an error since we need the user to set the period initially
   605  		if settings.Allowance.Period == 0 {
   606  			WriteError(w, Error{"period needs to be set if it hasn't been set before"}, http.StatusBadRequest)
   607  			return
   608  		}
   609  
   610  		// If Hosts is still 0 set to the sane default
   611  		if settings.Allowance.Hosts == 0 {
   612  			settings.Allowance.Hosts = modules.DefaultAllowance.Hosts
   613  		}
   614  
   615  		// If Renew Window is still 0 set to the sane default
   616  		if settings.Allowance.RenewWindow == 0 {
   617  			settings.Allowance.RenewWindow = settings.Allowance.Period / 2
   618  		}
   619  
   620  		// If Expected Storage is still 0 set to the sane default
   621  		if settings.Allowance.ExpectedStorage == 0 {
   622  			settings.Allowance.ExpectedStorage = modules.DefaultAllowance.ExpectedStorage
   623  		}
   624  
   625  		// If Expected Upload is still 0 set to the sane default
   626  		if settings.Allowance.ExpectedUpload == 0 {
   627  			settings.Allowance.ExpectedUpload = modules.DefaultAllowance.ExpectedUpload
   628  		}
   629  
   630  		// If Expected Download is still 0 set to the sane default
   631  		if settings.Allowance.ExpectedDownload == 0 {
   632  			settings.Allowance.ExpectedDownload = modules.DefaultAllowance.ExpectedDownload
   633  		}
   634  
   635  		// If Expected Redundancy is still 0 set to the sane default
   636  		if settings.Allowance.ExpectedRedundancy == 0 {
   637  			settings.Allowance.ExpectedRedundancy = modules.DefaultAllowance.ExpectedRedundancy
   638  		}
   639  	}
   640  
   641  	// Scan the download speed limit. (optional parameter)
   642  	if d := req.FormValue("maxdownloadspeed"); d != "" {
   643  		var downloadSpeed int64
   644  		if _, err := fmt.Sscan(d, &downloadSpeed); err != nil {
   645  			WriteError(w, Error{"unable to parse downloadspeed: " + err.Error()}, http.StatusBadRequest)
   646  			return
   647  		}
   648  		settings.MaxDownloadSpeed = downloadSpeed
   649  	}
   650  	// Scan the upload speed limit. (optional parameter)
   651  	if u := req.FormValue("maxuploadspeed"); u != "" {
   652  		var uploadSpeed int64
   653  		if _, err := fmt.Sscan(u, &uploadSpeed); err != nil {
   654  			WriteError(w, Error{"unable to parse uploadspeed: " + err.Error()}, http.StatusBadRequest)
   655  			return
   656  		}
   657  		settings.MaxUploadSpeed = uploadSpeed
   658  	}
   659  	// Scan the checkforipviolation flag.
   660  	if ipc := req.FormValue("checkforipviolation"); ipc != "" {
   661  		var ipviolationcheck bool
   662  		if _, err := fmt.Sscan(ipc, &ipviolationcheck); err != nil {
   663  			WriteError(w, Error{"unable to parse checkforipviolation: " + err.Error()}, http.StatusBadRequest)
   664  			return
   665  		}
   666  		settings.IPViolationsCheck = ipviolationcheck
   667  	}
   668  
   669  	// Set the settings in the renter.
   670  	err := api.renter.SetSettings(settings)
   671  	if err != nil {
   672  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
   673  		return
   674  	}
   675  	WriteSuccess(w)
   676  }
   677  
   678  // renterContractCancelHandler handles the API call to cancel a specific Renter contract.
   679  func (api *API) renterContractCancelHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   680  	var fcid types.FileContractID
   681  	if err := fcid.LoadString(req.FormValue("id")); err != nil {
   682  		WriteError(w, Error{"unable to parse id:" + err.Error()}, http.StatusBadRequest)
   683  		return
   684  	}
   685  	err := api.renter.CancelContract(fcid)
   686  	if err != nil {
   687  		WriteError(w, Error{"unable to cancel contract:" + err.Error()}, http.StatusBadRequest)
   688  		return
   689  	}
   690  	WriteSuccess(w)
   691  }
   692  
   693  // renterContractsHandler handles the API call to request the Renter's
   694  // contracts. Active and renewed contracts are returned by default
   695  //
   696  // Contracts are returned for Compatibility and are the contracts returned from
   697  // renter.Contracts()
   698  //
   699  // Inactive contracts are contracts that are not currently being used by the
   700  // renter because they are !goodForRenew, but have endheights that are in the
   701  // future so could potentially become active again
   702  //
   703  // Active contracts are contracts that the renter is actively using to store
   704  // data and can upload, download, and renew. These contracts are GoodForUpload
   705  // and GoodForRenew
   706  //
   707  // Refreshed contracts are contracts that are in the current period and were
   708  // refreshed due to running out of funds. A new contract that replaced a
   709  // refreshed contract can either be in Active or Disabled contracts. These
   710  // contracts are broken out as to not double count the data recorded in the
   711  // contract.
   712  //
   713  // Disabled Contracts are contracts that are no longer active as there are Not
   714  // GoodForUpload and Not GoodForRenew but still have endheights in the current
   715  // period.
   716  //
   717  // Expired contracts are contracts who's endheights are in the past.
   718  //
   719  // ExpiredRefreshed contracts are refreshed contracts who's endheights are in
   720  // the past.
   721  //
   722  // Recoverable contracts are contracts of the renter that are recovered from the
   723  // blockchain by using the renter's seed.
   724  func (api *API) renterContractsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   725  	// Parse flags
   726  	var disabled, inactive, expired, recoverable bool
   727  	var err error
   728  	if s := req.FormValue("disabled"); s != "" {
   729  		disabled, err = scanBool(s)
   730  		if err != nil {
   731  			WriteError(w, Error{"unable to parse disabled:" + err.Error()}, http.StatusBadRequest)
   732  			return
   733  		}
   734  	}
   735  	if s := req.FormValue("inactive"); s != "" {
   736  		inactive, err = scanBool(s)
   737  		if err != nil {
   738  			WriteError(w, Error{"unable to parse inactive:" + err.Error()}, http.StatusBadRequest)
   739  			return
   740  		}
   741  	}
   742  	if s := req.FormValue("expired"); s != "" {
   743  		expired, err = scanBool(s)
   744  		if err != nil {
   745  			WriteError(w, Error{"unable to parse expired:" + err.Error()}, http.StatusBadRequest)
   746  			return
   747  		}
   748  	}
   749  	if s := req.FormValue("recoverable"); s != "" {
   750  		recoverable, err = scanBool(s)
   751  		if err != nil {
   752  			WriteError(w, Error{"unable to parse recoverable:" + err.Error()}, http.StatusBadRequest)
   753  			return
   754  		}
   755  	}
   756  
   757  	// Parse the renter's contracts into their appropriate categories
   758  	contracts := api.parseRenterContracts(disabled, inactive, expired)
   759  
   760  	// Get recoverable contracts
   761  	var recoverableContracts []modules.RecoverableContract
   762  	if recoverable {
   763  		recoverableContracts = api.renter.RecoverableContracts()
   764  	}
   765  	contracts.RecoverableContracts = recoverableContracts
   766  
   767  	WriteJSON(w, contracts)
   768  }
   769  
   770  // parseRenterContracts categorized the Renter's contracts from Contracts() and
   771  // OldContracts().
   772  func (api *API) parseRenterContracts(disabled, inactive, expired bool) RenterContracts {
   773  	var rc RenterContracts
   774  	for _, c := range api.renter.Contracts() {
   775  		var size uint64
   776  		if len(c.Transaction.FileContractRevisions) != 0 {
   777  			size = c.Transaction.FileContractRevisions[0].NewFileSize
   778  		}
   779  
   780  		// Fetch host address
   781  		var netAddress modules.NetAddress
   782  		hdbe, exists := api.renter.Host(c.HostPublicKey)
   783  		if exists {
   784  			netAddress = hdbe.NetAddress
   785  		}
   786  
   787  		// Build the contract.
   788  		contract := RenterContract{
   789  			DownloadSpending:          c.DownloadSpending,
   790  			EndHeight:                 c.EndHeight,
   791  			Fees:                      c.TxnFee.Add(c.SiafundFee).Add(c.ContractFee),
   792  			GoodForUpload:             c.Utility.GoodForUpload,
   793  			GoodForRenew:              c.Utility.GoodForRenew,
   794  			HostPublicKey:             c.HostPublicKey,
   795  			HostVersion:               hdbe.Version,
   796  			ID:                        c.ID,
   797  			LastTransaction:           c.Transaction,
   798  			NetAddress:                netAddress,
   799  			RenterFunds:               c.RenterFunds,
   800  			Size:                      size,
   801  			StartHeight:               c.StartHeight,
   802  			StorageSpending:           c.StorageSpending,
   803  			StorageSpendingDeprecated: c.StorageSpending,
   804  			TotalCost:                 c.TotalCost,
   805  			UploadSpending:            c.UploadSpending,
   806  		}
   807  
   808  		// Determine contract status
   809  		refreshed := api.renter.RefreshedContract(c.ID)
   810  		active := c.Utility.GoodForUpload && c.Utility.GoodForRenew && !refreshed
   811  		passive := !c.Utility.GoodForUpload && c.Utility.GoodForRenew && !refreshed
   812  		disabledContract := disabled && !active && !passive && !refreshed
   813  
   814  		// A contract can either be active, passive, refreshed, or disabled
   815  		statusErr := active && passive && refreshed || active && refreshed || active && passive || passive && refreshed
   816  		if statusErr {
   817  			build.Critical("Contract has multiple status types, this should never happen")
   818  		} else if active {
   819  			rc.ActiveContracts = append(rc.ActiveContracts, contract)
   820  		} else if passive {
   821  			rc.PassiveContracts = append(rc.PassiveContracts, contract)
   822  		} else if refreshed {
   823  			rc.RefreshedContracts = append(rc.RefreshedContracts, contract)
   824  		} else if disabledContract {
   825  			rc.DisabledContracts = append(rc.DisabledContracts, contract)
   826  		}
   827  
   828  		// Record InactiveContracts and Contracts for compatibility
   829  		if !active && inactive {
   830  			rc.InactiveContracts = append(rc.InactiveContracts, contract)
   831  		}
   832  		rc.Contracts = append(rc.Contracts, contract)
   833  	}
   834  
   835  	// Get current block height for reference
   836  	currentPeriod := api.renter.CurrentPeriod()
   837  	for _, c := range api.renter.OldContracts() {
   838  		var size uint64
   839  		if len(c.Transaction.FileContractRevisions) != 0 {
   840  			size = c.Transaction.FileContractRevisions[0].NewFileSize
   841  		}
   842  
   843  		// Fetch host address
   844  		var netAddress modules.NetAddress
   845  		hdbe, exists := api.renter.Host(c.HostPublicKey)
   846  		if exists {
   847  			netAddress = hdbe.NetAddress
   848  		}
   849  
   850  		// Build contract
   851  		contract := RenterContract{
   852  			DownloadSpending:          c.DownloadSpending,
   853  			EndHeight:                 c.EndHeight,
   854  			Fees:                      c.TxnFee.Add(c.SiafundFee).Add(c.ContractFee),
   855  			GoodForUpload:             c.Utility.GoodForUpload,
   856  			GoodForRenew:              c.Utility.GoodForRenew,
   857  			HostPublicKey:             c.HostPublicKey,
   858  			HostVersion:               hdbe.Version,
   859  			ID:                        c.ID,
   860  			LastTransaction:           c.Transaction,
   861  			NetAddress:                netAddress,
   862  			RenterFunds:               c.RenterFunds,
   863  			Size:                      size,
   864  			StartHeight:               c.StartHeight,
   865  			StorageSpending:           c.StorageSpending,
   866  			StorageSpendingDeprecated: c.StorageSpending,
   867  			TotalCost:                 c.TotalCost,
   868  			UploadSpending:            c.UploadSpending,
   869  		}
   870  
   871  		// Determine contract status
   872  		refreshed := api.renter.RefreshedContract(c.ID)
   873  		currentPeriodContract := c.StartHeight >= currentPeriod
   874  		expiredContract := expired && !currentPeriodContract && !refreshed
   875  		expiredRefreshed := expired && !currentPeriodContract && refreshed
   876  		refreshedContract := refreshed && currentPeriodContract
   877  		disabledContract := disabled && !refreshed && currentPeriodContract
   878  
   879  		// A contract can only be refreshed, disabled, expired, or expired refreshed
   880  		if expiredContract {
   881  			rc.ExpiredContracts = append(rc.ExpiredContracts, contract)
   882  		} else if expiredRefreshed {
   883  			rc.ExpiredRefreshedContracts = append(rc.ExpiredRefreshedContracts, contract)
   884  		} else if refreshedContract {
   885  			rc.RefreshedContracts = append(rc.RefreshedContracts, contract)
   886  		} else if disabledContract {
   887  			rc.DisabledContracts = append(rc.DisabledContracts, contract)
   888  		}
   889  
   890  		// Record inactive contracts for compatibility
   891  		if inactive && currentPeriodContract {
   892  			rc.InactiveContracts = append(rc.InactiveContracts, contract)
   893  		}
   894  	}
   895  
   896  	return rc
   897  }
   898  
   899  // renterClearDownloadsHandler handles the API call to request to clear the download queue.
   900  func (api *API) renterClearDownloadsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   901  	var afterTime time.Time
   902  	beforeTime := types.EndOfTime
   903  	beforeStr, afterStr := req.FormValue("before"), req.FormValue("after")
   904  	if beforeStr != "" {
   905  		beforeInt, err := strconv.ParseInt(beforeStr, 10, 64)
   906  		if err != nil {
   907  			WriteError(w, Error{"parsing integer value for parameter `before` failed: " + err.Error()}, http.StatusBadRequest)
   908  			return
   909  		}
   910  		beforeTime = time.Unix(0, beforeInt)
   911  	}
   912  	if afterStr != "" {
   913  		afterInt, err := strconv.ParseInt(afterStr, 10, 64)
   914  		if err != nil {
   915  			WriteError(w, Error{"parsing integer value for parameter `after` failed: " + err.Error()}, http.StatusBadRequest)
   916  			return
   917  		}
   918  		afterTime = time.Unix(0, afterInt)
   919  	}
   920  
   921  	err := api.renter.ClearDownloadHistory(afterTime, beforeTime)
   922  	if err != nil {
   923  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
   924  		return
   925  	}
   926  	WriteSuccess(w)
   927  }
   928  
   929  // renterDownloadsHandler handles the API call to request the download queue.
   930  func (api *API) renterDownloadsHandler(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
   931  	var downloads []DownloadInfo
   932  	for _, di := range api.renter.DownloadHistory() {
   933  		downloads = append(downloads, DownloadInfo{
   934  			Destination:     di.Destination,
   935  			DestinationType: di.DestinationType,
   936  			Filesize:        di.Length,
   937  			Length:          di.Length,
   938  			Offset:          di.Offset,
   939  			SiaPath:         di.SiaPath,
   940  
   941  			Completed:            di.Completed,
   942  			EndTime:              di.EndTime,
   943  			Error:                di.Error,
   944  			Received:             di.Received,
   945  			StartTime:            di.StartTime,
   946  			StartTimeUnix:        di.StartTimeUnix,
   947  			TotalDataTransferred: di.TotalDataTransferred,
   948  		})
   949  	}
   950  	WriteJSON(w, RenterDownloadQueue{
   951  		Downloads: downloads,
   952  	})
   953  }
   954  
   955  // renterRecoveryScanHandlerPOST handles the API call to /renter/recoveryscan.
   956  func (api *API) renterRecoveryScanHandlerPOST(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   957  	if err := api.renter.InitRecoveryScan(); err != nil {
   958  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
   959  		return
   960  	}
   961  	WriteSuccess(w)
   962  }
   963  
   964  // renterRecoveryScanHandlerGET handles the API call to /renter/recoveryscan.
   965  func (api *API) renterRecoveryScanHandlerGET(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   966  	scanInProgress, height := api.renter.RecoveryScanStatus()
   967  	WriteJSON(w, RenterRecoveryStatusGET{
   968  		ScanInProgress: scanInProgress,
   969  		ScannedHeight:  height,
   970  	})
   971  }
   972  
   973  // renterRenameHandler handles the API call to rename a file entry in the
   974  // renter.
   975  func (api *API) renterRenameHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   976  	newSiaPathStr := req.FormValue("newsiapath")
   977  	siaPath, err := modules.NewSiaPath(ps.ByName("siapath"))
   978  	if err != nil {
   979  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
   980  		return
   981  	}
   982  	newSiaPath, err := modules.NewSiaPath(newSiaPathStr)
   983  	if err != nil {
   984  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
   985  		return
   986  	}
   987  	err = api.renter.RenameFile(siaPath, newSiaPath)
   988  	if err != nil {
   989  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
   990  		return
   991  	}
   992  	WriteSuccess(w)
   993  }
   994  
   995  // renterFileHandler handles GET requests to the /renter/file/:siapath API endpoint.
   996  func (api *API) renterFileHandlerGET(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   997  	siaPath, err := modules.NewSiaPath(ps.ByName("siapath"))
   998  	if err != nil {
   999  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
  1000  		return
  1001  	}
  1002  	file, err := api.renter.File(siaPath)
  1003  	if err != nil {
  1004  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
  1005  		return
  1006  	}
  1007  	WriteJSON(w, RenterFile{
  1008  		File: file,
  1009  	})
  1010  }
  1011  
  1012  // renterFileHandler handles POST requests to the /renter/file/:siapath API endpoint.
  1013  func (api *API) renterFileHandlerPOST(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
  1014  	newTrackingPath := req.FormValue("trackingpath")
  1015  	stuck := req.FormValue("stuck")
  1016  	siaPath, err := modules.NewSiaPath(ps.ByName("siapath"))
  1017  	if err != nil {
  1018  		WriteError(w, Error{"unable to parse siapath" + err.Error()}, http.StatusBadRequest)
  1019  		return
  1020  	}
  1021  	// Handle changing the tracking path of a file.
  1022  	if newTrackingPath != "" {
  1023  		if err := api.renter.SetFileTrackingPath(siaPath, newTrackingPath); err != nil {
  1024  			WriteError(w, Error{fmt.Sprintf("unable set tracking path: %v", err)}, http.StatusBadRequest)
  1025  			return
  1026  		}
  1027  	}
  1028  	// Handle changing the 'stuck' status of a file.
  1029  	if stuck != "" {
  1030  		s, err := strconv.ParseBool(stuck)
  1031  		if err != nil {
  1032  			WriteError(w, Error{"unable to parse 'stuck' arg"}, http.StatusBadRequest)
  1033  			return
  1034  		}
  1035  		if err := api.renter.SetFileStuck(siaPath, s); err != nil {
  1036  			WriteError(w, Error{"failed to change file 'stuck' status: " + err.Error()}, http.StatusBadRequest)
  1037  			return
  1038  		}
  1039  	}
  1040  	WriteSuccess(w)
  1041  }
  1042  
  1043  // renterFilesHandler handles the API call to list all of the files.
  1044  func (api *API) renterFilesHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
  1045  	var c bool
  1046  	var err error
  1047  	if cached := req.FormValue("cached"); cached != "" {
  1048  		c, err = strconv.ParseBool(cached)
  1049  		if err != nil {
  1050  			WriteError(w, Error{"unable to parse 'cached' arg"}, http.StatusBadRequest)
  1051  			return
  1052  		}
  1053  	}
  1054  	files, err := api.renter.FileList(modules.RootSiaPath(), true, c)
  1055  	if err != nil {
  1056  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
  1057  		return
  1058  	}
  1059  	WriteJSON(w, RenterFiles{
  1060  		Files: files,
  1061  	})
  1062  }
  1063  
  1064  // renterPricesHandler reports the expected costs of various actions given the
  1065  // renter settings and the set of available hosts.
  1066  func (api *API) renterPricesHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
  1067  	allowance := modules.Allowance{}
  1068  	// Scan the allowance amount. (optional parameter)
  1069  	if f := req.FormValue("funds"); f != "" {
  1070  		funds, ok := scanAmount(f)
  1071  		if !ok {
  1072  			WriteError(w, Error{"unable to parse funds"}, http.StatusBadRequest)
  1073  			return
  1074  		}
  1075  		allowance.Funds = funds
  1076  	}
  1077  	// Scan the number of hosts to use. (optional parameter)
  1078  	if h := req.FormValue("hosts"); h != "" {
  1079  		var hosts uint64
  1080  		if _, err := fmt.Sscan(h, &hosts); err != nil {
  1081  			WriteError(w, Error{"unable to parse hosts: " + err.Error()}, http.StatusBadRequest)
  1082  			return
  1083  		} else if hosts != 0 && hosts < requiredHosts {
  1084  			WriteError(w, Error{fmt.Sprintf("insufficient number of hosts, need at least %v but have %v", requiredHosts, hosts)}, http.StatusBadRequest)
  1085  		} else {
  1086  			allowance.Hosts = hosts
  1087  		}
  1088  	}
  1089  	// Scan the period. (optional parameter)
  1090  	if p := req.FormValue("period"); p != "" {
  1091  		var period types.BlockHeight
  1092  		if _, err := fmt.Sscan(p, &period); err != nil {
  1093  			WriteError(w, Error{"unable to parse period: " + err.Error()}, http.StatusBadRequest)
  1094  			return
  1095  		}
  1096  		allowance.Period = types.BlockHeight(period)
  1097  	}
  1098  	// Scan the renew window. (optional parameter)
  1099  	if rw := req.FormValue("renewwindow"); rw != "" {
  1100  		var renewWindow types.BlockHeight
  1101  		if _, err := fmt.Sscan(rw, &renewWindow); err != nil {
  1102  			WriteError(w, Error{"unable to parse renewwindow: " + err.Error()}, http.StatusBadRequest)
  1103  			return
  1104  		} else if renewWindow != 0 && types.BlockHeight(renewWindow) < requiredRenewWindow {
  1105  			WriteError(w, Error{fmt.Sprintf("renew window is too small, must be at least %v blocks but have %v blocks", requiredRenewWindow, renewWindow)}, http.StatusBadRequest)
  1106  			return
  1107  		} else {
  1108  			allowance.RenewWindow = types.BlockHeight(renewWindow)
  1109  		}
  1110  	}
  1111  
  1112  	// Check for partially set allowance, which can happen since hosts and renew
  1113  	// window can be optional fields. Checking here instead of assigning values
  1114  	// above so that an empty allowance can still be submitted
  1115  	if !reflect.DeepEqual(allowance, modules.Allowance{}) {
  1116  		if allowance.Funds.Cmp(types.ZeroCurrency) == 0 {
  1117  			WriteError(w, Error{fmt.Sprint("Allowance not set correctly, `funds` parameter left empty")}, http.StatusBadRequest)
  1118  			return
  1119  		}
  1120  		if allowance.Period == 0 {
  1121  			WriteError(w, Error{fmt.Sprint("Allowance not set correctly, `period` parameter left empty")}, http.StatusBadRequest)
  1122  			return
  1123  		}
  1124  		if allowance.Hosts == 0 {
  1125  			WriteError(w, Error{fmt.Sprint("Allowance not set correctly, `hosts` parameter left empty")}, http.StatusBadRequest)
  1126  			return
  1127  		}
  1128  		if allowance.RenewWindow == 0 {
  1129  			WriteError(w, Error{fmt.Sprint("Allowance not set correctly, `renewwindow` parameter left empty")}, http.StatusBadRequest)
  1130  			return
  1131  		}
  1132  	}
  1133  
  1134  	estimate, a, err := api.renter.PriceEstimation(allowance)
  1135  	if err != nil {
  1136  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
  1137  		return
  1138  	}
  1139  	WriteJSON(w, RenterPricesGET{
  1140  		RenterPriceEstimation: estimate,
  1141  		Allowance:             a,
  1142  	})
  1143  }
  1144  
  1145  // renterDeleteHandler handles the API call to delete a file entry from the
  1146  // renter.
  1147  func (api *API) renterDeleteHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
  1148  	siaPath, err := modules.NewSiaPath(ps.ByName("siapath"))
  1149  	if err != nil {
  1150  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
  1151  		return
  1152  	}
  1153  	err = api.renter.DeleteFile(siaPath)
  1154  	if err != nil {
  1155  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
  1156  		return
  1157  	}
  1158  
  1159  	WriteSuccess(w)
  1160  }
  1161  
  1162  // renterCancelDownloadHandler handles the API call to cancel a download.
  1163  func (api *API) renterCancelDownloadHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
  1164  	// Get the id.
  1165  	id := req.FormValue("id")
  1166  	if id == "" {
  1167  		WriteError(w, Error{"id not specified"}, http.StatusBadRequest)
  1168  		return
  1169  	}
  1170  	// Get the download from the map and delete it.
  1171  	api.downloadMu.Lock()
  1172  	cancel, ok := api.downloads[id]
  1173  	delete(api.downloads, id)
  1174  	api.downloadMu.Unlock()
  1175  	if !ok {
  1176  		WriteError(w, Error{"download for id not found"}, http.StatusBadRequest)
  1177  		return
  1178  	}
  1179  	// Cancel download and delete it from the map.
  1180  	cancel()
  1181  	WriteSuccess(w)
  1182  }
  1183  
  1184  // renterDownloadHandler handles the API call to download a file.
  1185  func (api *API) renterDownloadHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
  1186  	params, err := parseDownloadParameters(w, req, ps)
  1187  	if err != nil {
  1188  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
  1189  		return
  1190  	}
  1191  	if params.Async {
  1192  		var cancel func()
  1193  		id := hex.EncodeToString(fastrand.Bytes(16))
  1194  		cancel, err = api.renter.DownloadAsync(params, func(_ error) error {
  1195  			api.downloadMu.Lock()
  1196  			delete(api.downloads, id)
  1197  			api.downloadMu.Unlock()
  1198  			return nil
  1199  		})
  1200  		if err == nil {
  1201  			w.Header().Set("ID", id)
  1202  			api.downloadMu.Lock()
  1203  			api.downloads[id] = cancel
  1204  			api.downloadMu.Unlock()
  1205  		}
  1206  	} else {
  1207  		err = api.renter.Download(params)
  1208  	}
  1209  	if err != nil {
  1210  		WriteError(w, Error{"download failed: " + err.Error()}, http.StatusInternalServerError)
  1211  		return
  1212  	}
  1213  	if params.Httpwriter == nil {
  1214  		// `httpresp=true` causes writes to w before this line is run, automatically
  1215  		// adding `200 Status OK` code to response. Calling this results in a
  1216  		// multiple calls to WriteHeaders() errors.
  1217  		WriteSuccess(w)
  1218  		return
  1219  	}
  1220  }
  1221  
  1222  // renterDownloadAsyncHandler handles the API call to download a file asynchronously.
  1223  func (api *API) renterDownloadAsyncHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
  1224  	req.ParseForm()
  1225  	req.Form.Set("async", "true")
  1226  	api.renterDownloadHandler(w, req, ps)
  1227  }
  1228  
  1229  // parseDownloadParameters parses the download parameters passed to the
  1230  // /renter/download endpoint. Validation of these parameters is done by the
  1231  // renter.
  1232  func parseDownloadParameters(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (modules.RenterDownloadParameters, error) {
  1233  	destination := req.FormValue("destination")
  1234  
  1235  	// The offset and length in bytes.
  1236  	offsetparam := req.FormValue("offset")
  1237  	lengthparam := req.FormValue("length")
  1238  
  1239  	// Determines whether the response is written to response body.
  1240  	httprespparam := req.FormValue("httpresp")
  1241  
  1242  	// Determines whether to return on completion of download or straight away.
  1243  	// If httprespparam is present, this parameter is ignored.
  1244  	asyncparam := req.FormValue("async")
  1245  
  1246  	// Parse the offset and length parameters.
  1247  	var offset, length uint64
  1248  	if len(offsetparam) > 0 {
  1249  		_, err := fmt.Sscan(offsetparam, &offset)
  1250  		if err != nil {
  1251  			return modules.RenterDownloadParameters{}, errors.AddContext(err, "could not decode the offset as uint64")
  1252  		}
  1253  	}
  1254  	if len(lengthparam) > 0 {
  1255  		_, err := fmt.Sscan(lengthparam, &length)
  1256  		if err != nil {
  1257  			return modules.RenterDownloadParameters{}, errors.AddContext(err, "could not decode the offset as uint64")
  1258  		}
  1259  	}
  1260  
  1261  	// Parse the httpresp parameter.
  1262  	httpresp, err := scanBool(httprespparam)
  1263  	if err != nil {
  1264  		return modules.RenterDownloadParameters{}, errors.AddContext(err, "httpresp parameter could not be parsed")
  1265  	}
  1266  
  1267  	// Parse the async parameter.
  1268  	async, err := scanBool(asyncparam)
  1269  	if err != nil {
  1270  		return modules.RenterDownloadParameters{}, errors.AddContext(err, "async parameter could not be parsed")
  1271  	}
  1272  
  1273  	siaPath, err := modules.NewSiaPath(ps.ByName("siapath"))
  1274  	if err != nil {
  1275  		return modules.RenterDownloadParameters{}, errors.AddContext(err, "error parsing the siapath")
  1276  	}
  1277  
  1278  	dp := modules.RenterDownloadParameters{
  1279  		Destination: destination,
  1280  		Async:       async,
  1281  		Length:      length,
  1282  		Offset:      offset,
  1283  		SiaPath:     siaPath,
  1284  	}
  1285  	if httpresp {
  1286  		dp.Httpwriter = w
  1287  	}
  1288  
  1289  	return dp, nil
  1290  }
  1291  
  1292  // renterStreamHandler handles downloads from the /renter/stream endpoint
  1293  func (api *API) renterStreamHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
  1294  	siaPath, err := modules.NewSiaPath(ps.ByName("siapath"))
  1295  	if err != nil {
  1296  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
  1297  		return
  1298  	}
  1299  	fileName, streamer, err := api.renter.Streamer(siaPath)
  1300  	if err != nil {
  1301  		WriteError(w, Error{fmt.Sprintf("failed to create download streamer: %v", err)},
  1302  			http.StatusInternalServerError)
  1303  		return
  1304  	}
  1305  	defer streamer.Close()
  1306  	http.ServeContent(w, req, fileName, time.Time{}, streamer)
  1307  }
  1308  
  1309  // renterUploadHandler handles the API call to upload a file.
  1310  func (api *API) renterUploadHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
  1311  	// Get the source path.
  1312  	source := req.FormValue("source")
  1313  	// Source must be absolute path.
  1314  	if !filepath.IsAbs(source) {
  1315  		WriteError(w, Error{"source must be an absolute path"}, http.StatusBadRequest)
  1316  		return
  1317  	}
  1318  	// Check whether existing file should be overwritten
  1319  	var err error
  1320  	force := false
  1321  	if f := req.FormValue("force"); f != "" {
  1322  		force, err = strconv.ParseBool(f)
  1323  		if err != nil {
  1324  			WriteError(w, Error{"unable to parse 'force' parameter: " + err.Error()}, http.StatusBadRequest)
  1325  			return
  1326  		}
  1327  	}
  1328  	// Parse the erasure coder.
  1329  	ec, err := parseErasureCodingParameters(req.FormValue("datapieces"), req.FormValue("paritypieces"))
  1330  	if err != nil {
  1331  		WriteError(w, Error{"unable to parse erasure code settings" + err.Error()}, http.StatusBadRequest)
  1332  		return
  1333  	}
  1334  
  1335  	// Call the renter to upload the file.
  1336  	siaPath, err := modules.NewSiaPath(ps.ByName("siapath"))
  1337  	if err != nil {
  1338  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
  1339  		return
  1340  	}
  1341  	err = api.renter.Upload(modules.FileUploadParams{
  1342  		Source:      source,
  1343  		SiaPath:     siaPath,
  1344  		ErasureCode: ec,
  1345  		Force:       force,
  1346  	})
  1347  	if err != nil {
  1348  		WriteError(w, Error{"upload failed: " + err.Error()}, http.StatusInternalServerError)
  1349  		return
  1350  	}
  1351  	WriteSuccess(w)
  1352  }
  1353  
  1354  // renterUploadStreamHandler handles the API call to upload a file using a
  1355  // stream.
  1356  func (api *API) renterUploadStreamHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
  1357  	// Parse the query params.
  1358  	queryForm, err := url.ParseQuery(req.URL.RawQuery)
  1359  	if err != nil {
  1360  		WriteError(w, Error{"failed to parse query params"}, http.StatusBadRequest)
  1361  		return
  1362  	}
  1363  	// Check whether existing file should be overwritten
  1364  	force := false
  1365  	if f := queryForm.Get("force"); f != "" {
  1366  		force, err = strconv.ParseBool(f)
  1367  		if err != nil {
  1368  			WriteError(w, Error{"unable to parse 'force' parameter: " + err.Error()}, http.StatusBadRequest)
  1369  			return
  1370  		}
  1371  	}
  1372  	// Check whether existing file should be repaired
  1373  	repair := false
  1374  	if r := queryForm.Get("repair"); r != "" {
  1375  		repair, err = strconv.ParseBool(r)
  1376  		if err != nil {
  1377  			WriteError(w, Error{"unable to parse 'repair' parameter: " + err.Error()}, http.StatusBadRequest)
  1378  			return
  1379  		}
  1380  	}
  1381  	// Parse the erasure coder.
  1382  	ec, err := parseErasureCodingParameters(queryForm.Get("datapieces"), queryForm.Get("paritypieces"))
  1383  	if err != nil && !repair {
  1384  		WriteError(w, Error{"unable to parse erasure code settings" + err.Error()}, http.StatusBadRequest)
  1385  		return
  1386  	}
  1387  	if repair && ec != nil {
  1388  		WriteError(w, Error{"can't provide erasure code settings when doing a repair"}, http.StatusBadRequest)
  1389  		return
  1390  	}
  1391  
  1392  	// Call the renter to upload the file.
  1393  	siaPath, err := modules.NewSiaPath(ps.ByName("siapath"))
  1394  	if err != nil {
  1395  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
  1396  		return
  1397  	}
  1398  	up := modules.FileUploadParams{
  1399  		SiaPath:     siaPath,
  1400  		ErasureCode: ec,
  1401  		Force:       force,
  1402  		Repair:      repair,
  1403  	}
  1404  	err = api.renter.UploadStreamFromReader(up, req.Body)
  1405  	if err != nil {
  1406  		WriteError(w, Error{"upload failed: " + err.Error()}, http.StatusInternalServerError)
  1407  		return
  1408  	}
  1409  	WriteSuccess(w)
  1410  }
  1411  
  1412  // renterValidateSiaPathHandler handles the API call that validates a siapath
  1413  func (api *API) renterValidateSiaPathHandler(w http.ResponseWriter, _ *http.Request, ps httprouter.Params) {
  1414  	// Try and create a new siapath, this will validate the potential siapath
  1415  	_, err := modules.NewSiaPath(ps.ByName("siapath"))
  1416  	if err != nil {
  1417  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
  1418  		return
  1419  	}
  1420  	WriteSuccess(w)
  1421  }
  1422  
  1423  // renterDirHandlerGET handles the API call to query a directory
  1424  func (api *API) renterDirHandlerGET(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
  1425  	var siaPath modules.SiaPath
  1426  	var err error
  1427  	str := ps.ByName("siapath")
  1428  	if str == "" || str == "/" {
  1429  		siaPath = modules.RootSiaPath()
  1430  	} else {
  1431  		siaPath, err = modules.NewSiaPath(str)
  1432  	}
  1433  	if err != nil {
  1434  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
  1435  		return
  1436  	}
  1437  	directories, err := api.renter.DirList(siaPath)
  1438  	if err != nil {
  1439  		WriteError(w, Error{"failed to get directory contents:" + err.Error()}, http.StatusInternalServerError)
  1440  		return
  1441  	}
  1442  	files, err := api.renter.FileList(siaPath, false, true)
  1443  	if err != nil {
  1444  		WriteError(w, Error{"failed to get file infos:" + err.Error()}, http.StatusInternalServerError)
  1445  		return
  1446  	}
  1447  	WriteJSON(w, RenterDirectory{
  1448  		Directories: directories,
  1449  		Files:       files,
  1450  	})
  1451  	return
  1452  }
  1453  
  1454  // renterDirHandlerPOST handles the API call to create, delete and rename a
  1455  // directory
  1456  func (api *API) renterDirHandlerPOST(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
  1457  	// Parse action
  1458  	action := req.FormValue("action")
  1459  	if action == "" {
  1460  		WriteError(w, Error{"you must set the action you wish to execute"}, http.StatusInternalServerError)
  1461  		return
  1462  	}
  1463  	siaPath, err := modules.NewSiaPath(ps.ByName("siapath"))
  1464  	if err != nil {
  1465  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
  1466  		return
  1467  	}
  1468  	if action == "create" {
  1469  		// Call the renter to create directory
  1470  		err := api.renter.CreateDir(siaPath)
  1471  		if err != nil {
  1472  			WriteError(w, Error{"failed to create directory: " + err.Error()}, http.StatusInternalServerError)
  1473  			return
  1474  		}
  1475  		WriteSuccess(w)
  1476  		return
  1477  	}
  1478  	if action == "delete" {
  1479  		err := api.renter.DeleteDir(siaPath)
  1480  		if err != nil {
  1481  			WriteError(w, Error{"failed to delete directory: " + err.Error()}, http.StatusInternalServerError)
  1482  			return
  1483  		}
  1484  		WriteSuccess(w)
  1485  		return
  1486  	}
  1487  	if action == "rename" {
  1488  		newSiaPath, err := modules.NewSiaPath(req.FormValue("newsiapath"))
  1489  		if err != nil {
  1490  			WriteError(w, Error{"failed to parse newsiapath: " + err.Error()}, http.StatusBadRequest)
  1491  			return
  1492  		}
  1493  		err = api.renter.RenameDir(siaPath, newSiaPath)
  1494  		if err != nil {
  1495  			WriteError(w, Error{"failed to rename directory: " + err.Error()}, http.StatusInternalServerError)
  1496  			return
  1497  		}
  1498  		WriteSuccess(w)
  1499  		return
  1500  	}
  1501  
  1502  	// Report that no calls were made
  1503  	WriteError(w, Error{"no calls were made, please check your submission and try again"}, http.StatusInternalServerError)
  1504  	return
  1505  }