123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356 |
- /*
- * Rufs - Remote Union File System
- *
- * Copyright 2017 Matthias Ladkau. All rights reserved.
- *
- * This Source Code Form is subject to the terms of the MIT
- * License, If a copy of the MIT License was not distributed with this
- * file, You can obtain one at https://opensource.org/licenses/MIT.
- */
- package rufs
- import (
- "bytes"
- "crypto/tls"
- "encoding/gob"
- "encoding/json"
- "fmt"
- "io"
- "os"
- "path"
- "regexp"
- "sort"
- "strings"
- "sync"
- "time"
- "unicode/utf8"
- "devt.de/krotik/common/bitutil"
- "devt.de/krotik/common/fileutil"
- "devt.de/krotik/common/stringutil"
- "devt.de/krotik/rufs/config"
- "devt.de/krotik/rufs/node"
- )
- /*
- Tree models a Rufs client which combines several branches.
- */
- type Tree struct {
- client *node.Client // RPC client
- treeLock *sync.RWMutex // Lock for maps
- root *treeItem // Tree root item
- branches []map[string]string // Added working branches
- branchesAll []map[string]string // All added branches also not working
- mapping []map[string]interface{} // Mappings from working branches
- mappingAll []map[string]interface{} // All used mappings
- }
- /*
- NewTree creates a new tree.
- */
- func NewTree(cfg map[string]interface{}, cert *tls.Certificate) (*Tree, error) {
- var err error
- var t *Tree
- // Make sure the given config is ok
- if err = config.CheckTreeConfig(cfg); err == nil {
- // Create RPC client
- c := node.NewClient(fileutil.ConfStr(cfg, config.TreeSecret), cert)
- // Create the tree
- t = &Tree{c, &sync.RWMutex{}, &treeItem{make(map[string]*treeItem),
- []string{}, []bool{}}, []map[string]string{},
- []map[string]string{}, []map[string]interface{}{},
- []map[string]interface{}{}}
- }
- return t, err
- }
- /*
- Config returns the current tree configuration as a JSON string.
- */
- func (t *Tree) Config() string {
- t.treeLock.RLock()
- defer t.treeLock.RUnlock()
- out, _ := json.MarshalIndent(map[string]interface{}{
- "branches": t.branches,
- "tree": t.mapping,
- }, "", " ")
- return string(out)
- }
- /*
- SetMapping adds a given tree mapping configuration in a JSON string.
- */
- func (t *Tree) SetMapping(config string) error {
- var err error
- var conf map[string][]map[string]interface{}
- // Unmarshal the config
- if err = json.Unmarshal([]byte(config), &conf); err == nil {
- // Reset the whole tree
- t.Reset(true)
- if branches, ok := conf["branches"]; ok {
- for _, b := range branches {
- t.AddBranch(b["branch"].(string), b["rpc"].(string), b["fingerprint"].(string))
- }
- }
- if mounts, ok := conf["tree"]; ok {
- for _, m := range mounts {
- t.AddMapping(m["path"].(string), m["branch"].(string), m["writeable"].(bool))
- }
- }
- }
- return err
- }
- /*
- KnownBranches returns a map of all known branches (active or not reachable).
- Caution: This map contains also the map of active branches with their fingerprints
- it should only be used for read operations.
- */
- func (t *Tree) KnownBranches() map[string]map[string]string {
- ret := make(map[string]map[string]string)
- t.treeLock.RLock()
- t.treeLock.RUnlock()
- for _, b := range t.branchesAll {
- ret[b["branch"]] = b
- }
- return ret
- }
- /*
- ActiveBranches returns a list of all known active branches and their fingerprints.
- */
- func (t *Tree) ActiveBranches() ([]string, []string) {
- return t.client.Peers()
- }
- /*
- NotReachableBranches returns a map of all known branches which couldn't be
- reached. The map contains the name and the definition of the branch.
- */
- func (t *Tree) NotReachableBranches() map[string]map[string]string {
- ret := make(map[string]map[string]string)
- t.treeLock.RLock()
- t.treeLock.RUnlock()
- activeBranches := make(map[string]map[string]string)
- for _, b := range t.branches {
- activeBranches[b["branch"]] = b
- }
- for _, b := range t.branchesAll {
- name := b["branch"]
- if _, ok := activeBranches[name]; !ok {
- ret[name] = b
- }
- }
- return ret
- }
- /*
- PingBranch sends a ping to a remote branch and returns its fingerprint or an error.
- */
- func (t *Tree) PingBranch(node string, rpc string) (string, error) {
- _, fp, err := t.client.SendPing(node, rpc)
- return fp, err
- }
- /*
- Reset either resets only all mounts or if the branches flag is specified
- also all known branches.
- */
- func (t *Tree) Reset(branches bool) {
- if branches {
- peers, _ := t.client.Peers()
- for _, p := range peers {
- t.client.RemovePeer(p)
- }
- t.branches = []map[string]string{}
- t.branchesAll = []map[string]string{}
- }
- t.treeLock.Lock()
- defer t.treeLock.Unlock()
- t.mapping = []map[string]interface{}{}
- t.mappingAll = []map[string]interface{}{}
- t.root = &treeItem{make(map[string]*treeItem), []string{}, []bool{}}
- }
- /*
- Refresh refreshes all known branches and mappings. Only reachable branches will
- be mapped into the tree.
- */
- func (t *Tree) Refresh() {
- addBranches := make(map[string]map[string]string)
- delBranches := make(map[string]map[string]string)
- nrBranches := t.NotReachableBranches()
- // Check all known branches and decide if they should be added or removed
- t.treeLock.RLock()
- for _, data := range t.branchesAll {
- branchName := data["branch"]
- branchRPC := data["rpc"]
- _, knownAsNotWorking := nrBranches[branchName]
- // Ping the branch
- _, _, err := t.client.SendPing(branchName, branchRPC)
- if err == nil && knownAsNotWorking {
- // Success branch can now be reached
- addBranches[branchName] = data
- } else if err != nil && !knownAsNotWorking {
- // Failure branch can no longer be reached
- delBranches[branchName] = data
- }
- }
- t.treeLock.RUnlock()
- // Now lock the tree and add/remove branches
- t.treeLock.Lock()
- for i, b := range t.branches {
- branchName := b["branch"]
- if _, ok := delBranches[branchName]; ok {
- t.client.RemovePeer(branchName)
- t.branches = append(t.branches[:i], t.branches[i+1:]...)
- }
- }
- for _, b := range addBranches {
- branchName := b["branch"]
- branchRPC := b["rpc"]
- branchFingerprint := b["fingerprint"]
- t.client.RegisterPeer(branchName, branchRPC, branchFingerprint)
- t.branches = append(t.branches, b)
- }
- // Rebuild all mappings
- mappings := t.mappingAll
- t.mapping = []map[string]interface{}{}
- t.mappingAll = []map[string]interface{}{}
- t.root = &treeItem{make(map[string]*treeItem), []string{}, []bool{}}
- t.treeLock.Unlock()
- for _, m := range mappings {
- t.AddMapping(fmt.Sprint(m["path"]), fmt.Sprint(m["branch"]), m["writeable"].(bool))
- }
- }
- /*
- AddBranch adds a branch to the tree.
- */
- func (t *Tree) AddBranch(branchName string, branchRPC string, branchFingerprint string) error {
- branchMap := map[string]string{
- "branch": branchName,
- "rpc": branchRPC,
- "fingerprint": branchFingerprint,
- }
- t.branchesAll = append(t.branchesAll, branchMap)
- // First ping the branch and see if we get a response
- _, fp, err := t.client.SendPing(branchName, branchRPC)
- // Only add the branch as active if we've seen it
- if err == nil {
- if branchFingerprint != "" && branchFingerprint != fp {
- err = fmt.Errorf("Remote branch has an unexpected fingerprint\nPresented fingerprint: %s\nExpected fingerprint : %s", branchFingerprint, fp)
- } else {
- t.treeLock.Lock()
- defer t.treeLock.Unlock()
- if err = t.client.RegisterPeer(branchName, branchRPC, fp); err == nil {
- // Once we know and accepted the fingerprint we change it
- //
- // Remote branches will never change their fingerprint
- // during a single network session
- branchMap["fingerprint"] = fp
- t.branches = append(t.branches, branchMap) // Store the added branch
- }
- }
- }
- return err
- }
- /*
- AddMapping adds a mapping from tree path to a branch.
- */
- func (t *Tree) AddMapping(dir, branchName string, writable bool) error {
- t.treeLock.Lock()
- defer t.treeLock.Unlock()
- err := node.ErrUnknownTarget
- mappingMap := map[string]interface{}{
- "path": dir,
- "branch": branchName,
- "writeable": writable,
- }
- t.mappingAll = append(t.mappingAll, mappingMap)
- peers, _ := t.client.Peers()
- for _, p := range peers {
- if p == branchName {
- // Split the given path and add the mapping
- t.root.addMapping(createMappingPath(dir), branchName, writable)
- t.mapping = append(t.mapping, mappingMap)
- err = nil
- }
- }
- return err
- }
- /*
- String returns a string representation of this tree.
- */
- func (t *Tree) String() string {
- if t.treeLock != nil {
- t.treeLock.RLock()
- defer t.treeLock.RUnlock()
- }
- var buf bytes.Buffer
- buf.WriteString("/: ")
- if t != nil && t.root != nil {
- t.root.String(1, &buf)
- }
- return buf.String()
- }
- // Client API
- // ==========
- /*
- Dir returns file listings matching a given pattern of one or more directories.
- The contents of the given path is returned. Optionally, also the contents of
- all subdirectories can be returned if the recursive flag is set. The return
- values is a list of traversed directories and their corresponding contents.
- */
- func (t *Tree) Dir(dir string, pattern string, recursive bool, checksums bool) ([]string, [][]os.FileInfo, error) {
- var err error
- var dirs []string
- var fis [][]os.FileInfo
- // Compile pattern
- re, err := regexp.Compile(pattern)
- if err != nil {
- return nil, nil, err
- }
- t.treeLock.RLock()
- defer t.treeLock.RUnlock()
- // Stip off trailing slashes to normalize the input
- if strings.HasSuffix(dir, "/") {
- dir = dir[:len(dir)-1]
- }
- treeVisitor := func(item *treeItem, treePath string, branchPath []string, branches []string, writable []bool) {
- for _, b := range branches {
- var res []byte
- if err == nil {
- res, err = t.client.SendData(b, map[string]string{
- ParamAction: OpDir,
- ParamPath: path.Join(branchPath...),
- ParamPattern: fmt.Sprint(pattern),
- ParamRecursive: fmt.Sprint(recursive),
- ParamChecksums: fmt.Sprint(checksums),
- }, nil)
- if err == nil {
- var dest []interface{}
- // Unpack the result
- if err = gob.NewDecoder(bytes.NewBuffer(res)).Decode(&dest); err == nil {
- bdirs := dest[0].([]string)
- bfis := dest[1].([][]os.FileInfo)
- // Construct the actual tree path for the returned directories
- for i, d := range bdirs {
- bdirs[i] = path.Join(treePath, d)
- // Merge these results into the overall results
- found := false
- for j, dir := range dirs {
- // Check if a directory from the result is already
- // in the overall result
- if dir == bdirs[i] {
- found = true
- // Create a map of existing names to avoid duplicates
- existing := make(map[string]bool)
- for _, fi := range fis[j] {
- existing[fi.Name()] = true
- }
- // Only add new files to the overall result
- for _, fi := range bfis[i] {
- if _, ok := existing[fi.Name()]; !ok {
- fis[j] = append(fis[j], fi)
- }
- }
- }
- }
- if !found {
- // Just append if the directory is not in the
- // overall results yet
- dirs = append(dirs, bdirs[i])
- fis = append(fis, bfis[i])
- }
- }
- }
- }
- }
- }
- }
- t.root.findPathBranches("/", createMappingPath(dir), recursive, treeVisitor)
- // Add pseudo directories for mapping components which have no corresponding
- // real directories
- dirsMap := make(map[string]int)
- for i, d := range dirs {
- dirsMap[d] = i
- }
- t.root.findPathBranches("/", createMappingPath(dir), recursive,
- func(item *treeItem, treePath string, branchPath []string, branches []string, writable []bool) {
- if !strings.HasPrefix(treePath, dir) {
- return
- }
- idx, ok := dirsMap[treePath]
- if !ok {
- // Create the entry if it does not exist
- dirs = append(dirs, treePath)
- idx = len(dirs) - 1
- dirsMap[treePath] = idx
- fis = append(fis, []os.FileInfo{})
- }
- // Add pseudo dirs if a physical directory is not present
- for n := range item.children {
- found := false
- for _, fi := range fis[idx] {
- if fi.Name() == n {
- found = true
- break
- }
- }
- if found {
- continue
- }
- if re.MatchString(n) {
- // Append if it matches the pattern
- fis[idx] = append(fis[idx], &FileInfo{
- FiName: n,
- FiSize: 0,
- FiMode: os.FileMode(os.ModeDir | 0777),
- FiModTime: time.Time{},
- })
- }
- }
- })
- return dirs, fis, err
- }
- /*
- Stat returns information about a given item. Use this function to find out
- if a given path is a file or directory.
- */
- func (t *Tree) Stat(item string) (os.FileInfo, error) {
- dir, file := path.Split(item)
- _, fis, err := t.Dir(dir, file, false, true)
- if len(fis) == 1 {
- for _, fi := range fis[0] {
- if fi.Name() == file {
- return fi, err
- }
- }
- }
- if err == nil {
- err = &node.Error{
- Type: node.ErrRemoteAction,
- Detail: os.ErrNotExist.Error(),
- IsNotExist: true,
- }
- }
- return nil, err
- }
- /*
- Copy is a general purpose copy function which creates files and directories.
- Destination must be a directory. A non-existing destination
- directory will be created.
- */
- func (t *Tree) Copy(src []string, dst string,
- updFunc func(file string, writtenBytes, totalBytes, currentFile, totalFiles int64)) error {
- var err error
- var relPaths []string
- files := make(map[string]os.FileInfo) // Make sure any file is only copied once
- paths := make(map[string]string)
- // Add files to be copied to items
- for _, s := range src {
- var fi os.FileInfo
- fi, err = t.Stat(s)
- if fi, err = t.Stat(s); fi != nil {
- if fi.IsDir() {
- // Find all files inside directories
- if dirs, fis, err := t.Dir(s, "", true, false); err == nil {
- for i, d := range dirs {
- for _, fi2 := range fis[i] {
- if !fi2.IsDir() {
- // Calculate the relative path by removing
- // source path from the absolute path
- relPath := path.Join(d, fi2.Name())[len(s):]
- relPath = path.Join("/"+fi.Name(), relPath)
- relPaths = append(relPaths, relPath)
- files[relPath] = fi2
- paths[relPath] = path.Join(d, fi2.Name())
- }
- }
- }
- }
- } else {
- // Single files are just added - these files will always
- // be at the root of the destination
- relPath := "/" + fi.Name()
- relPaths = append(relPaths, relPath)
- files[relPath] = fi
- paths[relPath] = s
- }
- }
- if err != nil {
- err = fmt.Errorf("Cannot stat %v: %v", s, err.Error())
- break
- }
- }
- if err == nil {
- var allFiles, cnt int64
- // Copy all found files
- allFiles = int64(len(files))
- for _, k := range relPaths {
- var totalSize, totalTransferred int64
- cnt++
- fi := files[k]
- totalSize = fi.Size()
- srcFile := paths[k]
- err = t.CopyFile(srcFile, path.Join(dst, k), func(b int) {
- if b >= 0 {
- totalTransferred += int64(b)
- updFunc(k, totalTransferred, totalSize, cnt, allFiles)
- } else {
- updFunc(k, int64(b), totalSize, cnt, allFiles)
- }
- })
- if err != nil {
- err = fmt.Errorf("Cannot copy %v to %v: %v", srcFile, dst, err.Error())
- break
- }
- }
- }
- return err
- }
- /*
- Sync operations
- */
- const (
- SyncCreateDirectory = "Create directory"
- SyncCopyFile = "Copy file"
- SyncRemoveDirectory = "Remove directory"
- SyncRemoveFile = "Remove file"
- )
- /*
- Sync a given destination with a given source directory. After this command has
- finished the dstDir will have the same files and directories as the srcDir.
- */
- func (t *Tree) Sync(srcDir string, dstDir string, recursive bool,
- updFunc func(op, srcFile, dstFile string, writtenBytes, totalBytes, currentFile, totalFiles int64)) error {
- var currentFile, totalFiles int64
- t.treeLock.RLock()
- defer t.treeLock.RUnlock()
- // doSync syncs a given src directory
- doSync := func(dir string, finfos []os.FileInfo) error {
- sdir := path.Join(srcDir, dir)
- ddir := path.Join(dstDir, dir)
- // Query the corresponding destination to see what is there
- _, dstFis, err := t.Dir(ddir, "", false, true)
- if err == nil {
- fileMap := make(map[string]string) // Map to quickly lookup destination files
- dirMap := make(map[string]bool) // Map to quickly lookup destination directories
- if len(dstFis) > 0 {
- for _, fi := range dstFis[0] {
- if fi.IsDir() {
- dirMap[fi.Name()] = true
- } else {
- fileMap[fi.Name()] = fi.(*FileInfo).Checksum()
- }
- }
- }
- // Go through the given source file infos and see what needs to be copied
- for _, fi := range finfos {
- currentFile++
- // Check if we have a directory or a file
- if fi.IsDir() {
- if _, ok := dirMap[fi.Name()]; !ok {
- // Create all directories which aren't there
- if updFunc != nil {
- updFunc(SyncCreateDirectory, "", path.Join(ddir, fi.Name()), 0, 0, currentFile, totalFiles)
- }
- _, err = t.ItemOp(ddir, map[string]string{
- ItemOpAction: ItemOpActMkDir,
- ItemOpName: fi.Name(),
- })
- }
- // Remove existing directories from the map so we can
- // use the map to remove directories which shouldn't
- // be there
- delete(dirMap, fi.Name())
- } else {
- fsum, ok := fileMap[fi.Name()]
- if !ok || fsum != fi.(*FileInfo).Checksum() {
- var u func(b int)
- s := path.Join(sdir, fi.Name())
- d := path.Join(ddir, fi.Name())
- // Copy the file if it does not exist or the checksum
- // is not matching
- if updFunc != nil {
- var totalTransferred, totalSize int64
- totalSize = fi.Size()
- u = func(b int) {
- if b >= 0 {
- totalTransferred += int64(b)
- updFunc(SyncCopyFile, s, d, totalTransferred, totalSize, currentFile, totalFiles)
- } else {
- updFunc(SyncCopyFile, s, d, int64(b), totalSize, currentFile, totalFiles)
- }
- }
- }
- if err = t.CopyFile(s, d, u); err != nil && updFunc != nil {
- // Note at which point the error message was produced
- updFunc(SyncCopyFile, s, d, 0, fi.Size(), currentFile, totalFiles)
- }
- }
- // Remove existing files from the map so we can
- // use the map to remove files which shouldn't
- // be there
- delete(fileMap, fi.Name())
- }
- if err != nil {
- break
- }
- }
- if err == nil {
- // Remove files and directories which are in the destination but
- // not in the source
- for d := range dirMap {
- if err == nil {
- if updFunc != nil {
- p := path.Join(ddir, d)
- updFunc(SyncRemoveDirectory, "", p, 0, 0, currentFile, totalFiles)
- }
- _, err = t.ItemOp(ddir, map[string]string{
- ItemOpAction: ItemOpActDelete,
- ItemOpName: d,
- })
- }
- }
- for f := range fileMap {
- if err == nil {
- if updFunc != nil {
- p := path.Join(ddir, f)
- updFunc(SyncRemoveFile, "", p, 0, 0, currentFile, totalFiles)
- }
- _, err = t.ItemOp(ddir, map[string]string{
- ItemOpAction: ItemOpActDelete,
- ItemOpName: f,
- })
- }
- }
- }
- }
- return err
- }
- // We only query the source once otherwise we might end up in an
- // endless loop if for example the dstDir is a subdirectory of srcDir
- srcDirs, srcFis, err := t.Dir(srcDir, "", recursive, true)
- if err == nil {
- for _, fis := range srcFis {
- totalFiles += int64(len(fis))
- }
- for i, dir := range srcDirs {
- if err = doSync(relPath(dir, srcDir), srcFis[i]); err != nil {
- break
- }
- }
- }
- return err
- }
- /*
- CopyFile copies a given file using a simple io.Pipe.
- */
- func (t *Tree) CopyFile(srcPath, dstPath string, updFunc func(writtenBytes int)) error {
- var pw io.WriteCloser
- var err, rerr error
- t.treeLock.RLock()
- defer t.treeLock.RUnlock()
- // Use a pipe to stream the contents of the source file to the destination file
- pr, pw := io.Pipe()
- if updFunc != nil {
- // Wrap the writer of the pipe
- pw = &statusUpdatingWriter{pw, updFunc}
- }
- // Make sure the src exists
- if _, rerr = t.ReadFile(srcPath, []byte{}, 0); rerr == nil {
- // Read the source in a go routine
- go func() {
- rerr = t.ReadFileToBuffer(srcPath, pw)
- pw.Close()
- }()
- // Write the destination file - this will return once the
- // writer is closed
- err = t.WriteFileFromBuffer(dstPath, pr)
- }
- if rerr != nil {
- // Check if we got an empty file
- if IsEOF(rerr) {
- _, err = t.WriteFile(dstPath, nil, 0)
- updFunc(0) // Report the creation of the empty file
- rerr = nil
- } else {
- // Read errors are reported before write errors
- err = rerr
- }
- }
- pr.Close()
- return err
- }
- /*
- ReadFileToBuffer reads a complete file into a given buffer which implements
- io.Writer.
- */
- func (t *Tree) ReadFileToBuffer(spath string, buf io.Writer) error {
- var n int
- var err error
- var offset int64
- readBuf := make([]byte, DefaultReadBufferSize)
- for err == nil {
- n, err = t.ReadFile(spath, readBuf, offset)
- if err == nil {
- _, err = buf.Write(readBuf[:n])
- offset += int64(n)
- } else if IsEOF(err) {
- // We reached the end of the file
- err = nil
- break
- }
- }
- return err
- }
- /*
- ReadFile reads up to len(p) bytes into p from the given offset. It
- returns the number of bytes read (0 <= n <= len(p)) and any error
- encountered.
- */
- func (t *Tree) ReadFile(spath string, p []byte, offset int64) (int, error) {
- var err error
- var n int
- var success bool
- t.treeLock.RLock()
- defer t.treeLock.RUnlock()
- err = &node.Error{
- Type: node.ErrRemoteAction,
- Detail: os.ErrNotExist.Error(),
- IsNotExist: true,
- }
- dir, file := path.Split(spath)
- t.root.findPathBranches(dir, createMappingPath(dir), false,
- func(item *treeItem, treePath string, branchPath []string, branches []string, writable []bool) {
- for _, b := range branches {
- if !success { // Only try other branches if we didn't have a success before
- var res []byte
- rpath := path.Join(branchPath...)
- rpath = path.Join(rpath, file)
- if res, err = t.client.SendData(b, map[string]string{
- ParamAction: OpRead,
- ParamPath: rpath,
- ParamOffset: fmt.Sprint(offset),
- ParamSize: fmt.Sprint(len(p)),
- }, nil); err == nil {
- var dest []interface{}
- // Unpack the result
- if err = gob.NewDecoder(bytes.NewBuffer(res)).Decode(&dest); err == nil {
- n = dest[0].(int)
- buf := dest[1].([]byte)
- copy(p, buf)
- }
- }
- success = err == nil
- // Special case EOF
- if IsEOF(err) {
- success = true
- }
- }
- }
- })
- return n, err
- }
- /*
- WriteFileFromBuffer writes a complete file from a given buffer which implements
- io.Reader.
- */
- func (t *Tree) WriteFileFromBuffer(spath string, buf io.Reader) error {
- var err error
- var offset int64
- writeBuf := make([]byte, DefaultReadBufferSize)
- for err == nil {
- var n int
- if n, err = buf.Read(writeBuf); err == nil {
- _, err = t.WriteFile(spath, writeBuf[:n], offset)
- offset += int64(n)
- } else if IsEOF(err) {
- // We reached the end of the file
- t.WriteFile(spath, []byte{}, offset)
- err = nil
- break
- }
- }
- return err
- }
- /*
- WriteFile writes p into the given file from the given offset. It
- returns the number of written bytes and any error encountered.
- */
- func (t *Tree) WriteFile(spath string, p []byte, offset int64) (int, error) {
- var err error
- var n, totalCount, ignoreCount int
- t.treeLock.RLock()
- defer t.treeLock.RUnlock()
- dir, file := path.Split(spath)
- t.root.findPathBranches(dir, createMappingPath(dir), false,
- func(item *treeItem, treePath string, branchPath []string, branches []string, writable []bool) {
- for i, b := range branches {
- var res []byte
- if err == nil {
- totalCount++
- if !writable[i] {
- // Ignore all non-writable branches
- ignoreCount++
- continue
- }
- rpath := path.Join(branchPath...)
- rpath = path.Join(rpath, file)
- if res, err = t.client.SendData(b, map[string]string{
- ParamAction: OpWrite,
- ParamPath: rpath,
- ParamOffset: fmt.Sprint(offset),
- }, p); err == nil {
- err = gob.NewDecoder(bytes.NewBuffer(res)).Decode(&n)
- }
- }
- }
- })
- if err == nil && totalCount == ignoreCount {
- err = fmt.Errorf("All applicable branches for the requested path were mounted as not writable")
- }
- return n, err
- }
- /*
- ItemOp executes a file or directory specific operation which can either
- succeed or fail (e.g. rename or delete). Actions and parameters should
- be given in the opdata map.
- */
- func (t *Tree) ItemOp(dir string, opdata map[string]string) (bool, error) {
- var err error
- var ret, recurse bool
- var totalCount, ignoreCount, notFoundCount int
- t.treeLock.RLock()
- defer t.treeLock.RUnlock()
- data := make(map[string]string)
- for k, v := range opdata {
- data[k] = v
- }
- data[ParamAction] = OpItemOp
- // Check if we should recurse
- if r, ok := data[ItemOpName]; ok {
- recurse = strings.HasSuffix(r, "**")
- }
- t.root.findPathBranches(dir, createMappingPath(dir), recurse,
- func(item *treeItem, treePath string, branchPath []string,
- branches []string, writable []bool) {
- for i, b := range branches {
- var res []byte
- totalCount++
- if !writable[i] {
- // Ignore all non-writable branches
- ignoreCount++
- continue
- }
- if err == nil {
- data[ParamPath] = path.Join(branchPath...)
- res, err = t.client.SendData(b, data, nil)
- if rerr, ok := err.(*node.Error); ok && rerr.IsNotExist {
- // Only count the not exist errors as this might only
- // be true for some branches
- notFoundCount++
- err = nil
- } else if err == nil {
- var bres bool
- // Execute the OpItem function
- err = gob.NewDecoder(bytes.NewBuffer(res)).Decode(&bres)
- ret = ret || bres // One positive result is enough
- }
- }
- }
- })
- if totalCount == ignoreCount {
- err = fmt.Errorf("All applicable branches for the requested path were mounted as not writable")
- } else if totalCount == notFoundCount+ignoreCount {
- err = &node.Error{
- Type: node.ErrRemoteAction,
- Detail: os.ErrNotExist.Error(),
- IsNotExist: true,
- }
- }
- return ret, err
- }
- // Util functions
- // ==============
- /*
- createMappingPath properly splits a given path into a mapping path.
- */
- func createMappingPath(path string) []string {
- var ret []string
- for _, i := range strings.Split(path, "/") {
- if i == "" {
- // Ignore empty child names
- continue
- }
- ret = append(ret, i)
- }
- return ret
- }
- /*
- DirResultToString formats a given Dir result into a human-readable string.
- */
- func DirResultToString(paths []string, infos [][]os.FileInfo) string {
- var buf bytes.Buffer
- // Sort the paths
- sort.Sort(&dirResult{paths, infos})
- // Sort the FileInfos within the paths
- for _, fis := range infos {
- sort.Sort(fileInfoSlice(fis))
- }
- for i, p := range paths {
- var maxlen int
- fis := infos[i]
- buf.WriteString(p)
- buf.WriteString("\n")
- sizeStrings := make([]string, 0, len(fis))
- for _, fi := range fis {
- sizeString := bitutil.ByteSizeString(fi.Size(), false)
- if strings.HasSuffix(sizeString, " B") {
- sizeString += " " // Unit should always be 3 runes
- }
- if l := utf8.RuneCountInString(sizeString); l > maxlen {
- maxlen = l
- }
- sizeStrings = append(sizeStrings, sizeString)
- }
- for j, fi := range fis {
- sizeString := sizeStrings[j]
- sizePrefix := stringutil.GenerateRollingString(" ",
- maxlen-utf8.RuneCountInString(sizeString))
- if rfi, ok := fi.(*FileInfo); ok && rfi.FiChecksum != "" {
- buf.WriteString(fmt.Sprintf("%v %v%v %v [%s]\n", fi.Mode(), sizePrefix,
- sizeString, fi.Name(), rfi.Checksum()))
- } else {
- buf.WriteString(fmt.Sprintf("%v %v%v %v\n", fi.Mode(), sizePrefix,
- sizeString, fi.Name()))
- }
- }
- if i < len(paths)-1 {
- buf.WriteString("\n")
- }
- }
- return buf.String()
- }
- // Helper functions
- // ================
- // Helper function to normalise relative paths
- /*
- relPath create a normalized relative path by removing a given path prefix.
- */
- func relPath(path, prefix string) string {
- norm := func(path string) string {
- if !strings.HasPrefix(path, "/") {
- path = "/" + path
- }
- if strings.HasSuffix(path, "/") {
- path = path[:len(path)-1]
- }
- return path
- }
- path = norm(path)
- prefix = norm(prefix)
- if strings.HasPrefix(path, prefix) {
- path = path[len(prefix):]
- if path == "" {
- path = "/"
- }
- }
- return path
- }
- // Helper objects to sort dir results
- type dirResult struct {
- paths []string
- infos [][]os.FileInfo
- }
- func (r *dirResult) Len() int { return len(r.paths) }
- func (r *dirResult) Less(i, j int) bool { return r.paths[i] < r.paths[j] }
- func (r *dirResult) Swap(i, j int) {
- r.paths[i], r.paths[j] = r.paths[j], r.paths[i]
- r.infos[i], r.infos[j] = r.infos[j], r.infos[i]
- }
- type fileInfoSlice []os.FileInfo
- func (f fileInfoSlice) Len() int { return len(f) }
- func (f fileInfoSlice) Less(i, j int) bool { return f[i].Name() < f[j].Name() }
- func (f fileInfoSlice) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
- // Helper object to given status updates when copying files
- /*
- statusUpdatingWriter is an internal io.WriteCloser which is used for status
- updates.
- */
- type statusUpdatingWriter struct {
- io.WriteCloser
- statusUpdate func(writtenBytes int)
- }
- /*
- Write writes len(p) bytes from p to the writer.
- */
- func (w *statusUpdatingWriter) Write(p []byte) (int, error) {
- n, err := w.WriteCloser.Write(p)
- w.statusUpdate(n)
- return n, err
- }
- /*
- Close closes the writer.
- */
- func (w *statusUpdatingWriter) Close() error {
- w.statusUpdate(-1)
- return w.WriteCloser.Close()
- }
|