forked from austinbyers/GlobusFS
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cache.py
135 lines (107 loc) · 4.48 KB
/
cache.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
"""Handles metadata cache (memory) and file cache (local endpoint)."""
import os
import shutil
import stat
import time
class MetaData(object):
"""Keep filesystem metadata in memory."""
def __init__(self, api):
self.api = api
# Map file path to stat() file info dicts.
self.files = {}
# Map dirpath to list of files. This is technically redundant information,
# (we could read it from self.files), but this makes listdir() faster.
self.dirs = {}
self.NewFile('/', stat.S_IFDIR | 0755)
self._LoadRemoteDir('/')
def _LoadRemoteDir(self, path):
"""Load information from a remote directory if it isn't in memory already."""
if path in self.dirs:
return
data = self.api.EndpointList(path)
# Add list of files to the directory.
self.dirs[path] = [x['name'] for x in data]
# Add file metadata.
for file_info in data:
f_type = stat.S_IFDIR if file_info['type'] == 'dir' else stat.S_IFREG
permissions = int(file_info['permissions'], 8) # permissions are octal
now = time.time()
self.files[os.path.join(path, file_info['name'])] = {
'st_atime': now,
'st_mtime': now, # TODO: grab last_modified time from response
'st_ctime': now,
'st_nlink': 2,
'st_mode': (f_type | permissions),
'st_size': file_info['size']
}
##############
# Read #
##############
def Listdir(self, path):
"""List directory contents."""
self._LoadRemoteDir(path)
return ['.', '..'] + self.dirs[path]
def Stat(self, path):
"""Return stat() info for a file or None if the file doesn't exist."""
self._LoadRemoteDir(os.path.dirname(path))
return self.files.get(path, None)
##############
# Modify #
##############
def _AddFileToParentDir(self, path):
if path != '/':
self.dirs[os.path.dirname(path)].append(os.path.basename(path))
def _RemoveFileFromParentDir(self, path):
self.dirs[os.path.dirname(path)].remove(os.path.basename(path))
def ChangeFileSize(self, path, size):
self.files[path]['st_size'] = size
def NewDirectory(self, path):
"""Create a new entry for the directory."""
self.NewFile(path, stat.S_IFDIR | 0755) # TODO: use given mode rather than hard-code?
self.dirs[path] = []
def NewFile(self, path, mode):
"""Create a new entry for the given path."""
now = time.time()
self.files[path] = {'st_atime': now, 'st_mtime': now, 'st_ctime': now,
'st_nlink': 2, 'st_mode': mode, 'st_size': 0}
self._AddFileToParentDir(path)
def Remove(self, path):
"""Remove file entry."""
self.files[path] = None
self._RemoveFileFromParentDir(path)
def Rename(self, old_path, new_path):
"""Move a file entry to a new path."""
self.files[new_path] = self.files[old_path]
self._AddFileToParentDir(new_path)
self.Remove(old_path)
class FileCache(object):
"""Handles reading/writing to the file cache."""
def __init__(self, api, path):
"""Initialize cache under the local endpoint path."""
self.api = api
self.cache_dir = os.path.join(path, '.globusfs-cache')
if os.path.exists(self.cache_dir):
shutil.rmtree(self.cache_dir)
os.mkdir(self.cache_dir)
os.chmod(self.cache_dir, 0777)
self.cache = {} # Maps remote filepath to local file object.
def Destroy(self):
"""Destroy the cache."""
shutil.rmtree(self.cache_dir)
print 'Deleted local cache', self.cache_dir
def Get(self, path):
"""Get file descriptor for the given path."""
return self.cache[path]
def Create(self, path):
"""Create and open a new file."""
cache_file = os.path.join(self.cache_dir, os.path.basename(path))
self.cache[path] = open(cache_file, mode='w+')
def Open(self, path, flags):
"""Open the given file, downloading it if necessary."""
cache_file = os.path.join(self.cache_dir, os.path.basename(path))
if path not in self.cache:
if not self.api.CopyToLocal(path, cache_file):
return None
# Open the file and return a file handle.
self.cache[path] = os.fdopen(os.open(cache_file, flags))
return self.cache[path]