// Copyright (C) 2015 The Syncthing Authors. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at https://mozilla.org/MPL/2.0/. package model import ( "github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/sync" ) // deviceFolderFileDownloadState holds current download state of a file that // a remote device has advertised. blockIndexes represends indexes within // FileInfo.Blocks that the remote device already has, and version represents // the version of the file that the remote device is downloading. type deviceFolderFileDownloadState struct { blockIndexes []int version protocol.Vector blockSize int } // deviceFolderDownloadState holds current download state of all files that // a remote device is currently downloading in a specific folder. type deviceFolderDownloadState struct { mut sync.RWMutex files map[string]deviceFolderFileDownloadState } // Has returns whether a block at that specific index, and that specific version of the file // is currently available on the remote device for pulling from a temporary file. func (p *deviceFolderDownloadState) Has(file string, version protocol.Vector, index int) bool { p.mut.RLock() defer p.mut.RUnlock() local, ok := p.files[file] if !ok || !local.version.Equal(version) { return false } for _, existingIndex := range local.blockIndexes { if existingIndex == index { return true } } return false } // Update updates internal state of what has been downloaded into the temporary // files by the remote device for this specific folder. func (p *deviceFolderDownloadState) Update(updates []protocol.FileDownloadProgressUpdate) { p.mut.Lock() defer p.mut.Unlock() for _, update := range updates { local, ok := p.files[update.Name] if update.UpdateType == protocol.FileDownloadProgressUpdateTypeForget && ok && local.version.Equal(update.Version) { delete(p.files, update.Name) } else if update.UpdateType == protocol.FileDownloadProgressUpdateTypeAppend { if !ok { local = deviceFolderFileDownloadState{ blockIndexes: update.BlockIndexes, version: update.Version, blockSize: int(update.BlockSize), } } else if !local.version.Equal(update.Version) { local.blockIndexes = append(local.blockIndexes[:0], update.BlockIndexes...) local.version = update.Version local.blockSize = int(update.BlockSize) } else { local.blockIndexes = append(local.blockIndexes, update.BlockIndexes...) } p.files[update.Name] = local } } } func (p *deviceFolderDownloadState) BytesDownloaded() int64 { p.mut.RLock() defer p.mut.RUnlock() var res int64 for _, state := range p.files { // BlockSize is a new field introduced in 1.4.1, thus a fallback // is required (will potentially underrepresent downloaded bytes). if state.blockSize != 0 { res += int64(len(state.blockIndexes) * state.blockSize) } else { res += int64(len(state.blockIndexes) * protocol.MinBlockSize) } } return res } // GetBlockCounts returns a map filename -> number of blocks downloaded. func (p *deviceFolderDownloadState) GetBlockCounts() map[string]int { p.mut.RLock() res := make(map[string]int, len(p.files)) for name, state := range p.files { res[name] = len(state.blockIndexes) } p.mut.RUnlock() return res } // deviceDownloadState represents the state of all in progress downloads // for all folders of a specific device. type deviceDownloadState struct { mut sync.RWMutex folders map[string]*deviceFolderDownloadState } // Update updates internal state of what has been downloaded into the temporary // files by the remote device for this specific folder. func (t *deviceDownloadState) Update(folder string, updates []protocol.FileDownloadProgressUpdate) { if t == nil { return } t.mut.RLock() f, ok := t.folders[folder] t.mut.RUnlock() if !ok { f = &deviceFolderDownloadState{ mut: sync.NewRWMutex(), files: make(map[string]deviceFolderFileDownloadState), } t.mut.Lock() t.folders[folder] = f t.mut.Unlock() } f.Update(updates) } // Has returns whether block at that specific index, and that specific version of the file // is currently available on the remote device for pulling from a temporary file. func (t *deviceDownloadState) Has(folder, file string, version protocol.Vector, index int) bool { if t == nil { return false } t.mut.RLock() f, ok := t.folders[folder] t.mut.RUnlock() if !ok { return false } return f.Has(file, version, index) } // GetBlockCounts returns a map filename -> number of blocks downloaded for the // given folder. func (t *deviceDownloadState) GetBlockCounts(folder string) map[string]int { if t == nil { return nil } t.mut.RLock() defer t.mut.RUnlock() for name, state := range t.folders { if name == folder { return state.GetBlockCounts() } } return nil } func (t *deviceDownloadState) BytesDownloaded(folder string) int64 { if t == nil { return 0 } t.mut.RLock() defer t.mut.RUnlock() for name, state := range t.folders { if name == folder { return state.BytesDownloaded() } } return 0 } func newDeviceDownloadState() *deviceDownloadState { return &deviceDownloadState{ mut: sync.NewRWMutex(), folders: make(map[string]*deviceFolderDownloadState), } }