github.com/whyrusleeping/gx@v0.14.3/gxutil/get.go (about) 1 package gxutil 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "time" 9 10 stump "github.com/whyrusleeping/stump" 11 ) 12 13 type ErrAlreadyInstalled struct { 14 pkg string 15 } 16 17 func IsErrAlreadyInstalled(err error) bool { 18 _, ok := err.(ErrAlreadyInstalled) 19 return ok 20 } 21 22 func (eai ErrAlreadyInstalled) Error() string { 23 return fmt.Sprintf("package %s already installed", eai.pkg) 24 } 25 26 func (pm *PM) GetPackageTo(hash, out string) (*Package, error) { 27 var pkg Package 28 _, err := os.Stat(out) 29 if err == nil { 30 err := FindPackageInDir(&pkg, out) 31 if err == nil { 32 return &pkg, nil 33 } 34 35 stump.VLog("Target directory already exists but isn't a valid package, cleaning up...") 36 if oErr := os.RemoveAll(out); oErr != nil { 37 stump.Error("cannot purge existing target directory:", oErr) 38 return nil, oErr 39 } 40 } 41 42 if err != nil && !os.IsNotExist(err) { 43 return nil, err 44 } 45 46 if err := pm.tryFetch(hash, out); err != nil { 47 return nil, err 48 } 49 50 err = FindPackageInDir(&pkg, out) 51 if err != nil { 52 return nil, err 53 } 54 55 return &pkg, nil 56 } 57 58 func (pm *PM) CacheAndLinkPackage(ref, cacheloc, out string) error { 59 if err := pm.tryFetch(ref, cacheloc); err != nil { 60 return err 61 } 62 63 finfo, err := os.Lstat(out) 64 switch { 65 case err == nil: 66 if finfo.Mode()&os.ModeSymlink != 0 { 67 target, err := os.Readlink(out) 68 if err != nil { 69 return err 70 } 71 72 // expecting 'ref' to be '/ipfs/QmFoo/pkg' 73 if target != cacheloc { 74 panic("not handling dep changes yet") 75 } 76 77 // Link already exists 78 return nil 79 } else { 80 // TODO: should we force these to be links? 81 // we want to support people just cloning packages into place here, so how should we handle it here? 82 panic("not yet handling non-linked packages...") 83 } 84 case os.IsNotExist(err): 85 // ok 86 default: 87 return err 88 } 89 90 if err := os.MkdirAll(filepath.Dir(out), 0755); err != nil { 91 return err 92 } 93 94 // dir where the link goes 95 linkloc, _ := filepath.Split(out) 96 // relative path from link to cache 97 rel, err := filepath.Rel(linkloc, cacheloc) 98 if err != nil { 99 return err 100 } 101 return os.Symlink(rel, out) 102 } 103 104 func (pm *PM) tryFetch(hash, target string) error { 105 temp := target + ".part" 106 107 // check if already downloaded 108 _, err := os.Stat(target) 109 if err == nil { 110 stump.VLog("already fetched %s", target) 111 return nil 112 } 113 114 // check if a fetch was previously started and failed, cleanup if found 115 _, err = os.Stat(temp) 116 if err == nil { 117 stump.VLog("Found previously failed fetch, cleaning up...") 118 if err := os.RemoveAll(temp); err != nil { 119 stump.Error("cleaning up previous aborted transfer: %s", err) 120 } 121 } 122 123 begin := time.Now() 124 stump.VLog(" - fetching %s via ipfs api", hash) 125 defer func() { 126 stump.VLog(" - fetch finished in %s", time.Since(begin)) 127 }() 128 tries := 3 129 for i := 0; i < tries; i++ { 130 if err := pm.Shell().Get(hash, temp); err != nil { 131 stump.Error("from shell.Get(): %v", err) 132 133 rmerr := os.RemoveAll(temp) 134 if rmerr != nil { 135 stump.Error("cleaning up temp download directory: %s", rmerr) 136 } 137 138 if i == tries-1 { 139 return err 140 } 141 stump.Log("retrying fetch %s after a second...", hash) 142 time.Sleep(time.Second) 143 } else { 144 /* 145 if err := chmodR(temp, 0444); err != nil { 146 return err 147 } 148 */ 149 return os.Rename(temp, target) 150 } 151 } 152 panic("unreachable") 153 } 154 155 func chmodR(dir string, perm os.FileMode) error { 156 return filepath.Walk(dir, func(p string, info os.FileInfo, err error) error { 157 if p == dir { 158 return nil 159 } 160 161 if err == nil { 162 if info.Mode()&os.ModeSymlink != 0 { 163 return nil 164 } 165 166 perm := perm 167 if info.IsDir() { 168 perm |= 0111 169 } 170 171 return os.Chmod(p, perm) 172 } 173 return nil 174 }) 175 } 176 177 func FindPackageInDir(pkg interface{}, dir string) error { 178 if err := LoadPackageFile(pkg, filepath.Join(dir, PkgFileName)); err == nil { 179 return nil 180 } 181 182 name, err := PackageNameInDir(dir) 183 if err != nil { 184 return err 185 } 186 return LoadPackageFile(pkg, filepath.Join(dir, name, PkgFileName)) 187 } 188 189 func PackageNameInDir(dir string) (string, error) { 190 fs, err := ioutil.ReadDir(dir) 191 if err != nil { 192 return "", err 193 } 194 195 if len(fs) == 0 { 196 return "", fmt.Errorf("no package found in hashdir: %s", dir) 197 } 198 199 if len(fs) > 1 { 200 return "", fmt.Errorf("found multiple packages in hashdir: %s", dir) 201 } 202 203 return fs[0].Name(), nil 204 }