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  }