Resuming downloads
This page provides an overview of how download resumption is implemented
Resuming downloads is a useful feature that saves bandwidth and time by allowing the retrieval process to continue from where it left off, in case of interruptions or disconnections.
It calculates where to restart by determining the startBlock
when processing download requests. This computation occurs in the calculateShardParams
method, which interacts with the fileHandler
to establish the file size. The derived size is then employed to call the Seek()
method of the DownloadRequest
. This method adjusts the offset
field of the DownloadRequest
, which is subsequently used to calculate the startBlock
.
For local files, the system opens them in append mode if the file already exists and has a size that's greater than 0.
This resumption feature is illustrated in the code sections below. These functions together facilitate the operation of the download resumption process:
calculateShardsParams()
: Executes the actual resumption logic by calculating shard parameters. If there's an offset (indicating an existing incomplete file), it adjusts thestartBlock
accordingly.
func (req *DownloadRequest) calculateShardsParams(
fRef *fileref.FileRef, remotePathCB string) (chunksPerShard int64, err error) {
size := fRef.ActualFileSize
if req.contentMode == DOWNLOAD_CONTENT_THUMB {
if fRef.ActualThumbnailSize == 0 {
return 0, errors.New("invalid_request", "Thumbnail does not exist")
}
size = fRef.ActualThumbnailSize
}
req.size = size
req.encryptedKey = fRef.EncryptedKey
req.chunkSize = int(fRef.ChunkSize)
effectivePerShardSize := (size + int64(req.datashards) - 1) / int64(req.datashards)
effectiveBlockSize := fRef.ChunkSize
if fRef.EncryptedKey != "" {
effectiveBlockSize -= EncryptionHeaderSize + EncryptedDataPaddingSize
}
req.effectiveBlockSize = int(effectiveBlockSize)
chunksPerShard = (effectivePerShardSize + effectiveBlockSize - 1) / effectiveBlockSize
info, err := req.fileHandler.Stat()
if err != nil {
return 0, err
}
_, err = req.Seek(info.Size(), io.SeekStart)
if err != nil {
return 0, err
}
effectiveChunkSize := effectiveBlockSize * int64(req.datashards)
if req.offset > 0 {
req.startBlock += req.offset / effectiveChunkSize
}
if req.endBlock == 0 || req.endBlock > chunksPerShard {
req.endBlock = chunksPerShard
}
if req.startBlock >= req.endBlock {
err = errors.New("invalid_block_num", "start block should be less than end block")
return 0, err
}
return
}
prepareAndOpenLocalFile()
: Handles the local file, checking if it exists, and opening in append mode or creating it as needed.
func (a *Allocation) prepareAndOpenLocalFile(localPath string, remotePath string) (*os.File, string, bool, error) {
var toKeep bool
if !a.isInitialized() {
return nil, "", toKeep, notInitialized
}
var localFilePath string
// If the localPath has a file extension, treat it as a file. Otherwise, treat it as a directory.
if filepath.Ext(localPath) != "" {
localFilePath = localPath
} else {
localFileName := filepath.Base(remotePath)
localFilePath = filepath.Join(localPath, localFileName)
}
// Create necessary directories if they do not exist
dir := filepath.Dir(localFilePath)
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err := os.MkdirAll(dir, 0744); err != nil {
return nil, "", toKeep, err
}
}
var f *os.File
info, err := os.Stat(localFilePath)
if errors.Is(err, os.ErrNotExist) {
f, err = os.OpenFile(localFilePath, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return nil, "", toKeep, errors.Wrap(err, "Can't create local file")
}
} else {
f, err = os.OpenFile(localFilePath, os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return nil, "", toKeep, errors.Wrap(err, "Can't open local file in append mode")
}
if info.Size() > 0 {
toKeep = true
}
}
return f, localFilePath, toKeep, nil
}
Last updated