1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 from file_system import FileSystem, FileNotFoundError, StatInfo
6 from future import Future
7 from path_util import AssertIsValid, AssertIsDirectory, IsDirectory
10 def MoveTo(base, obj):
11 '''Returns an object as |obj| moved to |base|. That is,
12 MoveTo('foo/bar', {'a': 'b'}) -> {'foo': {'bar': {'a': 'b'}}}
14 AssertIsDirectory(base)
17 for k in base.rstrip('/').split('/'):
24 def MoveAllTo(base, obj):
25 '''Moves every value in |obj| to |base|. See MoveTo.
28 for key, value in obj.iteritems():
29 result[key] = MoveTo(base, value)
33 def _List(file_system):
34 '''Returns a list of '/' separated paths derived from |file_system|.
35 For example, {'index.html': '', 'www': {'file.txt': ''}} would return
36 ['index.html', 'www/file.txt'].
38 assert isinstance(file_system, dict)
40 def update_result(item, path):
42 if isinstance(item, dict):
45 result[path] = [p if isinstance(content, basestring) else (p + '/')
46 for p, content in item.iteritems()]
47 for subpath, subitem in item.iteritems():
48 update_result(subitem, path + subpath)
49 elif isinstance(item, basestring):
52 raise ValueError('Unsupported item type: %s' % type(item))
53 update_result(file_system, '')
57 class _StatTracker(object):
58 '''Maintains the versions of paths in a file system. The versions of files
59 are changed either by |Increment| or |SetVersion|. The versions of
60 directories are derived from the versions of files within it.
67 def Increment(self, path=None, by=1):
69 self._global_stat += by
71 self.SetVersion(path, self._path_stats.get(path, 0) + by)
73 def SetVersion(self, path, new_version):
75 raise ValueError('Only files have an incrementable stat, '
76 'but "%s" is a directory' % path)
78 # Update version of that file.
79 self._path_stats[path] = new_version
81 # Update all parent directory versions as well.
82 slash_index = 0 # (deliberately including '' in the dir paths)
83 while slash_index != -1:
84 dir_path = path[:slash_index] + '/'
85 self._path_stats[dir_path] = max(self._path_stats.get(dir_path, 0),
88 # Legacy support for '/' being the root of the file system rather
89 # than ''. Eventually when the path normalisation logic is complete
90 # this will be impossible and this logic will change slightly.
91 self._path_stats[''] = self._path_stats['/']
92 slash_index = path.find('/', slash_index + 1)
94 def GetVersion(self, path):
95 return self._global_stat + self._path_stats.get(path, 0)
98 class TestFileSystem(FileSystem):
99 '''A FileSystem backed by an object. Create with an object representing file
100 paths such that {'a': {'b': 'hello'}} will resolve Read('a/b') as 'hello',
101 Read('a/') as ['b'], and Stat determined by a value incremented via
105 def __init__(self, obj, relative_to=None, identity=None):
106 assert obj is not None
107 if relative_to is not None:
108 obj = MoveTo(relative_to, obj)
109 self._identity = identity or type(self).__name__
110 self._path_values = _List(obj)
111 self._stat_tracker = _StatTracker()
114 # FileSystem implementation.
117 def Read(self, paths):
119 if path not in self._path_values:
120 return FileNotFoundError.RaiseInFuture(
121 '%s not in %s' % (path, '\n'.join(self._path_values)))
122 return Future(value=dict((k, v) for k, v in self._path_values.iteritems()
126 return Future(value=())
128 def Stat(self, path):
129 read_result = self.ReadSingle(path).Get()
130 stat_result = StatInfo(str(self._stat_tracker.GetVersion(path)))
131 if isinstance(read_result, list):
132 stat_result.child_versions = dict(
134 str(self._stat_tracker.GetVersion('%s%s' % (path, file_result))))
135 for file_result in read_result)
142 def IncrementStat(self, path=None, by=1):
143 self._stat_tracker.Increment(path, by=by)
145 def GetIdentity(self):
146 return self._identity