forked from googlearchive/cloud-playground
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathurlfetch_tree.py
More file actions
189 lines (156 loc) · 5.54 KB
/
Copy pathurlfetch_tree.py
File metadata and controls
189 lines (156 loc) · 5.54 KB
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# Copyright 2012 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A mutable tree implementation that is backed by URL Fetch."""
import datetime
import httplib
import json
import urllib
from mimic.__mimic import common
from error import Abort
from . import settings
from . import shared
class UrlFetchTree(common.Tree):
"""An implementation of Tree backed by URL Fetch."""
def __init__(self, namespace, access_key):
if not namespace:
Abort(httplib.FORBIDDEN, 'Missing namespace')
if not access_key:
Abort(httplib.FORBIDDEN, 'Missing access key')
super(UrlFetchTree, self).__init__(namespace, access_key)
self.namespace = namespace
self.access_key = access_key
def __repr__(self):
return ('<{0} namespace={1!r}>'
.format(self.__class__.__name__, self.namespace))
def _ToFileURL(self, control_path, params):
params = params.copy()
params[common.config.PROJECT_ID_QUERY_PARAM] = self.namespace
url = '{}/{}?{}'.format(common.CONTROL_PREFIX, control_path,
urllib.urlencode(params))
playground_hostname = (settings.PLAYGROUND_USER_CONTENT_HOST or
settings.PLAYGROUND_HOSTS[0])
url = 'https://{0}{1}'.format(playground_hostname, url)
return url
def IsMutable(self):
return True
def RemoteGetFile(self, path):
"""Retrieve the file via URL Fetch.
Args:
path: The file path.
Returns:
The URL Fetch response.
"""
url = self._ToFileURL('file', {'path': path})
return shared.Fetch(self.access_key, url, method='GET')
def RemotePutFile(self, path, content):
"""Put the file via URL Fetch.
Args:
path: The file path.
content: The file contents.
Returns:
The URL Fetch response.
"""
url = self._ToFileURL('file', {'path': path})
resp = shared.Fetch(self.access_key, url, method='PUT', payload=content)
if resp.status_code != httplib.OK:
shared.e('{0} status code during HTTP PUT on {1}'
.format(resp.status_code, url))
return resp
def GetFileContents(self, path):
resp = self.RemoteGetFile(path)
if resp.status_code != httplib.OK:
return None
return resp.content
def GetFileSize(self, path):
contents = self.GetFileContents(path)
if contents is None:
return None
return len(contents)
def GetFileLastModified(self, path):
resp = self.RemoteGetFile(path)
if resp.status_code != httplib.OK:
return None
date_header = resp.headers['Last-Modified']
date = datetime.datetime.strptime(date_header, common.RFC_1123_DATE_FORMAT)
return date
def HasFile(self, path):
# root always exists, even if there are no files in the tree
if path == '': # pylint: disable-msg=C6403
return True
resp = self.RemoteGetFile(path)
if resp.status_code == httplib.OK:
return True
return False
def MoveFile(self, path, newpath):
"""Rename a file.
Args:
path: The file path to rename.
newpath: The new path.
Returns:
True if the move succeeded.
"""
url = self._ToFileURL('file', {'path': path, 'newpath': newpath})
resp = shared.Fetch(self.access_key, url, method='POST')
if resp.status_code != httplib.OK:
shared.e('{0} status code during HTTP POST on {1}'
.format(resp.status_code, url))
return True
def DeletePath(self, path):
"""Delete a file or directory.
Args:
path: The path to delete.
Returns:
True if the delete succeeded.
"""
url = self._ToFileURL('delete', {'path': path})
resp = shared.Fetch(self.access_key, url, method='POST')
if resp.status_code != httplib.OK:
shared.e('{0} status code during HTTP POST on {1}'
.format(resp.status_code, url))
return True
def Clear(self):
self.DeletePath('')
def SetFile(self, path, contents):
resp = self.RemotePutFile(path, contents)
assert resp.status_code == httplib.OK
def HasDirectory(self, path):
return bool(self.ListDirectory(path))
def ListDirectory(self, path):
"""List the current directory or tree contents.
Args:
path: The directory path to list or '' to access the entire tree.
Returns:
A list of files in the specified directory or tree.
"""
path = self._NormalizeDirectoryPath(path)
url = self._ToFileURL('dir', {})
resp = shared.Fetch(self.access_key, url, method='GET')
if resp.status_code != httplib.OK:
shared.e('{0} status code during HTTP GET on {1}'
.format(resp.status_code, url))
files = json.loads(resp.content)
paths = set()
for f in files:
candidate_path = f['path']
# 'path is None' means get all files recursively
if path is None:
paths.add(candidate_path)
continue
if not candidate_path.startswith(path):
continue
tail = candidate_path[len(path):]
# return tail if tail is a file otherwise return dir name (=first segment)
subpath = tail.split('/', 1)[0]
paths.add(subpath)
return sorted(paths)