github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/resource/context/internal/resourcedir.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package internal
     5  
     6  // TODO(ericsnow) Move this file elsewhere?
     7  //  (e.g. top-level resource pkg, charm/resource)
     8  
     9  import (
    10  	"io"
    11  
    12  	"github.com/juju/errors"
    13  	charmresource "gopkg.in/juju/charm.v6-unstable/resource"
    14  )
    15  
    16  // DirectorySpec identifies information for a resource directory.
    17  type DirectorySpec struct {
    18  	// Name is the resource name.
    19  	Name string
    20  
    21  	// Dirname is the path to the resource directory.
    22  	Dirname string
    23  
    24  	// Deps is the external dependencies of DirectorySpec.
    25  	Deps DirectorySpecDeps
    26  }
    27  
    28  // NewDirectorySpec returns a new directory spec for the given info.
    29  func NewDirectorySpec(dataDir, name string, deps DirectorySpecDeps) *DirectorySpec {
    30  	dirname := deps.Join(dataDir, name)
    31  
    32  	spec := &DirectorySpec{
    33  		Name:    name,
    34  		Dirname: dirname,
    35  
    36  		Deps: deps,
    37  	}
    38  	return spec
    39  }
    40  
    41  // Resolve returns the fully resolved file path, relative to the directory.
    42  func (spec DirectorySpec) Resolve(path ...string) string {
    43  	return spec.Deps.Join(append([]string{spec.Dirname}, path...)...)
    44  }
    45  
    46  // TODO(ericsnow) Make IsUpToDate a stand-alone function?
    47  
    48  // IsUpToDate determines whether or not the content matches the resource directory.
    49  func (spec DirectorySpec) IsUpToDate(content Content) (bool, error) {
    50  	filename := spec.Resolve(spec.Name)
    51  	ok, err := spec.Deps.FingerprintMatches(filename, content.Fingerprint)
    52  	return ok, errors.Trace(err)
    53  }
    54  
    55  // Initialize preps the spec'ed directory and returns it.
    56  func (spec DirectorySpec) Initialize() (*Directory, error) {
    57  	if err := spec.Deps.MkdirAll(spec.Dirname); err != nil {
    58  		return nil, errors.Annotate(err, "could not create resource dir")
    59  	}
    60  
    61  	return NewDirectory(&spec, spec.Deps), nil
    62  }
    63  
    64  // DirectorySpecDeps exposes the external depenedencies of DirectorySpec.
    65  type DirectorySpecDeps interface {
    66  	DirectoryDeps
    67  
    68  	// FingerprintMatches determines whether or not the identified file
    69  	// exists and has the provided fingerprint.
    70  	FingerprintMatches(filename string, fp charmresource.Fingerprint) (bool, error)
    71  
    72  	// Join exposes the functionality of filepath.Join().
    73  	Join(...string) string
    74  
    75  	// MkdirAll exposes the functionality of os.MkdirAll().
    76  	MkdirAll(string) error
    77  }
    78  
    79  // TempDirectorySpec represents a resource directory placed under a temporary data dir.
    80  type TempDirectorySpec struct {
    81  	*DirectorySpec
    82  
    83  	// CleanUp cleans up the temp directory in which the resource
    84  	// directory is placed.
    85  	CleanUp func() error
    86  }
    87  
    88  // NewTempDirectorySpec creates a new temp directory spec
    89  // for the given resource.
    90  func NewTempDirectorySpec(name string, deps TempDirDeps) (*TempDirectorySpec, error) {
    91  	tempDir, err := deps.NewTempDir()
    92  	if err != nil {
    93  		return nil, errors.Trace(err)
    94  	}
    95  
    96  	spec := &TempDirectorySpec{
    97  		DirectorySpec: NewDirectorySpec(tempDir, name, deps),
    98  		CleanUp: func() error {
    99  			return deps.RemoveDir(tempDir)
   100  		},
   101  	}
   102  	return spec, nil
   103  }
   104  
   105  // TempDirDeps exposes the external functionality needed by
   106  // NewTempDirectorySpec().
   107  type TempDirDeps interface {
   108  	DirectorySpecDeps
   109  
   110  	// NewTempDir returns the path to a new temporary directory.
   111  	NewTempDir() (string, error)
   112  
   113  	// RemoveDir deletes the specified directory.
   114  	RemoveDir(string) error
   115  }
   116  
   117  // Close implements io.Closer.
   118  func (spec TempDirectorySpec) Close() error {
   119  	if err := spec.CleanUp(); err != nil {
   120  		return errors.Annotate(err, "could not clean up temp dir")
   121  	}
   122  	return nil
   123  }
   124  
   125  // Directory represents a resource directory.
   126  type Directory struct {
   127  	*DirectorySpec
   128  
   129  	// Deps holds the external dependencies of the directory.
   130  	Deps DirectoryDeps
   131  }
   132  
   133  // NewDirectory returns a new directory for the provided spec.
   134  func NewDirectory(spec *DirectorySpec, deps DirectoryDeps) *Directory {
   135  	dir := &Directory{
   136  		DirectorySpec: spec,
   137  		Deps:          deps,
   138  	}
   139  	return dir
   140  }
   141  
   142  // Write writes all relevant files from the given source
   143  // to the directory.
   144  func (dir *Directory) Write(opened ContentSource) error {
   145  	// TODO(ericsnow) Also write the info file...
   146  
   147  	relPath := opened.Info().Path
   148  	if err := dir.WriteContent(relPath, opened.Content()); err != nil {
   149  		return errors.Trace(err)
   150  	}
   151  
   152  	return nil
   153  }
   154  
   155  // WriteContent writes the resource file to the given path
   156  // within the directory.
   157  func (dir *Directory) WriteContent(relPath string, content Content) error {
   158  	if len(relPath) == 0 {
   159  		// TODO(ericsnow) Use rd.readInfo().Path, like openResource() does?
   160  		return errors.NotImplementedf("")
   161  	}
   162  	filename := dir.Resolve(relPath)
   163  
   164  	target, err := dir.Deps.CreateWriter(filename)
   165  	if err != nil {
   166  		return errors.Annotate(err, "could not create new file for resource")
   167  	}
   168  	defer dir.Deps.CloseAndLog(target, filename)
   169  
   170  	if err := dir.Deps.WriteContent(target, content); err != nil {
   171  		return errors.Trace(err)
   172  	}
   173  
   174  	return nil
   175  }
   176  
   177  // DirectoryDeps exposes the external functionality needed by Directory.
   178  type DirectoryDeps interface {
   179  	// CreateWriter creates a new writer to which the resource file
   180  	// will be written.
   181  	CreateWriter(string) (io.WriteCloser, error)
   182  
   183  	// CloseAndLog closes the closer and logs any error.
   184  	CloseAndLog(io.Closer, string)
   185  
   186  	// WriteContent writes the content to the directory.
   187  	WriteContent(io.Writer, Content) error
   188  }