Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(47)

Side by Side Diff: third_party/gsutil/gslib/tab_complete.py

Issue 1377933002: [catapult] - Copy Telemetry's gsutilz over to third_party. (Closed) Base URL: https://github.com/catapult-project/catapult.git@master
Patch Set: Rename to gsutil. Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « third_party/gsutil/gslib/storage_url.py ('k') | third_party/gsutil/gslib/tests/__init__.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # -*- coding: utf-8 -*-
2 # Copyright 2014 Google Inc. All Rights Reserved.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 """Shell tab completion."""
16
17 import itertools
18 import json
19 import threading
20 import time
21
22 import boto
23
24 from boto.gs.acl import CannedACLStrings
25 from gslib.storage_url import IsFileUrlString
26 from gslib.storage_url import StorageUrlFromString
27 from gslib.storage_url import StripOneSlash
28 from gslib.util import GetTabCompletionCacheFilename
29 from gslib.util import GetTabCompletionLogFilename
30 from gslib.wildcard_iterator import CreateWildcardIterator
31
32 TAB_COMPLETE_CACHE_TTL = 15
33
34 _TAB_COMPLETE_MAX_RESULTS = 1000
35
36 _TIMEOUT_WARNING = """
37 Tab completion aborted (took >%ss), you may complete the command manually.
38 The timeout can be adjusted in the gsutil configuration file.
39 """.rstrip()
40
41
42 class CompleterType(object):
43 CLOUD_BUCKET = 'cloud_bucket'
44 CLOUD_OBJECT = 'cloud_object'
45 CLOUD_OR_LOCAL_OBJECT = 'cloud_or_local_object'
46 LOCAL_OBJECT = 'local_object'
47 LOCAL_OBJECT_OR_CANNED_ACL = 'local_object_or_canned_acl'
48 NO_OP = 'no_op'
49
50
51 class LocalObjectCompleter(object):
52 """Completer object for local files."""
53
54 def __init__(self):
55 # This is only safe to import if argcomplete is present in the install
56 # (which happens for Cloud SDK installs), so import on usage, not on load.
57 # pylint: disable=g-import-not-at-top
58 from argcomplete.completers import FilesCompleter
59 self.files_completer = FilesCompleter()
60
61 def __call__(self, prefix, **kwargs):
62 return self.files_completer(prefix, **kwargs)
63
64
65 class LocalObjectOrCannedACLCompleter(object):
66 """Completer object for local files and canned ACLs.
67
68 Currently, only Google Cloud Storage canned ACL names are supported.
69 """
70
71 def __init__(self):
72 self.local_object_completer = LocalObjectCompleter()
73
74 def __call__(self, prefix, **kwargs):
75 local_objects = self.local_object_completer(prefix, **kwargs)
76 canned_acls = [acl for acl in CannedACLStrings if acl.startswith(prefix)]
77 return local_objects + canned_acls
78
79
80 class TabCompletionCache(object):
81 """Cache for tab completion results."""
82
83 def __init__(self, prefix, results, timestamp, partial_results):
84 self.prefix = prefix
85 self.results = results
86 self.timestamp = timestamp
87 self.partial_results = partial_results
88
89 @staticmethod
90 def LoadFromFile(filename):
91 """Instantiates the cache from a file.
92
93 Args:
94 filename: The file to load.
95 Returns:
96 TabCompletionCache instance with loaded data or an empty cache
97 if the file cannot be loaded
98 """
99 try:
100 with open(filename, 'r') as fp:
101 cache_dict = json.loads(fp.read())
102 prefix = cache_dict['prefix']
103 results = cache_dict['results']
104 timestamp = cache_dict['timestamp']
105 partial_results = cache_dict['partial-results']
106 except Exception: # pylint: disable=broad-except
107 # Guarding against incompatible format changes in the cache file.
108 # Erring on the side of not breaking tab-completion in case of cache
109 # issues.
110 prefix = None
111 results = []
112 timestamp = 0
113 partial_results = False
114
115 return TabCompletionCache(prefix, results, timestamp, partial_results)
116
117 def GetCachedResults(self, prefix):
118 """Returns the cached results for prefix or None if not in cache."""
119 current_time = time.time()
120 if current_time - self.timestamp >= TAB_COMPLETE_CACHE_TTL:
121 return None
122
123 results = None
124
125 if prefix == self.prefix:
126 results = self.results
127 elif (not self.partial_results and prefix.startswith(self.prefix)
128 and prefix.count('/') == self.prefix.count('/')):
129 results = [x for x in self.results if x.startswith(prefix)]
130
131 if results is not None:
132 # Update cache timestamp to make sure the cache entry does not expire if
133 # the user is performing multiple completions in a single
134 # bucket/subdirectory since we can answer these requests from the cache.
135 # e.g. gs://prefix<tab> -> gs://prefix-mid<tab> -> gs://prefix-mid-suffix
136 self.timestamp = time.time()
137 return results
138
139 def UpdateCache(self, prefix, results, partial_results):
140 """Updates the in-memory cache with the results for the given prefix."""
141 self.prefix = prefix
142 self.results = results
143 self.partial_results = partial_results
144 self.timestamp = time.time()
145
146 def WriteToFile(self, filename):
147 """Writes out the cache to the given file."""
148 json_str = json.dumps({
149 'prefix': self.prefix,
150 'results': self.results,
151 'partial-results': self.partial_results,
152 'timestamp': self.timestamp,
153 })
154
155 try:
156 with open(filename, 'w') as fp:
157 fp.write(json_str)
158 except IOError:
159 pass
160
161
162 class CloudListingRequestThread(threading.Thread):
163 """Thread that performs a listing request for the given URL string."""
164
165 def __init__(self, wildcard_url_str, gsutil_api):
166 """Instantiates Cloud listing request thread.
167
168 Args:
169 wildcard_url_str: The URL to list.
170 gsutil_api: gsutil Cloud API instance to use.
171 """
172 super(CloudListingRequestThread, self).__init__()
173 self.daemon = True
174 self._wildcard_url_str = wildcard_url_str
175 self._gsutil_api = gsutil_api
176 self.results = None
177
178 def run(self):
179 it = CreateWildcardIterator(
180 self._wildcard_url_str, self._gsutil_api).IterAll(
181 bucket_listing_fields=['name'])
182 self.results = [
183 str(c) for c in itertools.islice(it, _TAB_COMPLETE_MAX_RESULTS)]
184
185
186 class TimeoutError(Exception):
187 pass
188
189
190 class CloudObjectCompleter(object):
191 """Completer object for Cloud URLs."""
192
193 def __init__(self, gsutil_api, bucket_only=False):
194 """Instantiates completer for Cloud URLs.
195
196 Args:
197 gsutil_api: gsutil Cloud API instance to use.
198 bucket_only: Whether the completer should only match buckets.
199 """
200 self._gsutil_api = gsutil_api
201 self._bucket_only = bucket_only
202
203 def _PerformCloudListing(self, wildcard_url, timeout):
204 """Perform a remote listing request for the given wildcard URL.
205
206 Args:
207 wildcard_url: The wildcard URL to list.
208 timeout: Time limit for the request.
209 Returns:
210 Cloud resources matching the given wildcard URL.
211 Raises:
212 TimeoutError: If the listing does not finish within the timeout.
213 """
214 request_thread = CloudListingRequestThread(wildcard_url, self._gsutil_api)
215 request_thread.start()
216 request_thread.join(timeout)
217
218 if request_thread.is_alive():
219 # This is only safe to import if argcomplete is present in the install
220 # (which happens for Cloud SDK installs), so import on usage, not on load.
221 # pylint: disable=g-import-not-at-top
222 import argcomplete
223 argcomplete.warn(_TIMEOUT_WARNING % timeout)
224 raise TimeoutError()
225
226 results = request_thread.results
227
228 return results
229
230 def __call__(self, prefix, **kwargs):
231 if not prefix:
232 prefix = 'gs://'
233 elif IsFileUrlString(prefix):
234 return []
235
236 wildcard_url = prefix + '*'
237 url = StorageUrlFromString(wildcard_url)
238 if self._bucket_only and not url.IsBucket():
239 return []
240
241 timeout = boto.config.getint('GSUtil', 'tab_completion_timeout', 5)
242 if timeout == 0:
243 return []
244
245 start_time = time.time()
246
247 cache = TabCompletionCache.LoadFromFile(GetTabCompletionCacheFilename())
248 cached_results = cache.GetCachedResults(prefix)
249
250 timing_log_entry_type = ''
251 if cached_results is not None:
252 results = cached_results
253 timing_log_entry_type = ' (from cache)'
254 else:
255 try:
256 results = self._PerformCloudListing(wildcard_url, timeout)
257 if self._bucket_only and len(results) == 1:
258 results = [StripOneSlash(results[0])]
259 partial_results = (len(results) == _TAB_COMPLETE_MAX_RESULTS)
260 cache.UpdateCache(prefix, results, partial_results)
261 except TimeoutError:
262 timing_log_entry_type = ' (request timeout)'
263 results = []
264
265 cache.WriteToFile(GetTabCompletionCacheFilename())
266
267 end_time = time.time()
268 num_results = len(results)
269 elapsed_seconds = end_time - start_time
270 _WriteTimingLog(
271 '%s results%s in %.2fs, %.2f results/second for prefix: %s\n' %
272 (num_results, timing_log_entry_type, elapsed_seconds,
273 num_results / elapsed_seconds, prefix))
274
275 return results
276
277
278 class CloudOrLocalObjectCompleter(object):
279 """Completer object for Cloud URLs or local files.
280
281 Invokes the Cloud object completer if the input looks like a Cloud URL and
282 falls back to local file completer otherwise.
283 """
284
285 def __init__(self, gsutil_api):
286 self.cloud_object_completer = CloudObjectCompleter(gsutil_api)
287 self.local_object_completer = LocalObjectCompleter()
288
289 def __call__(self, prefix, **kwargs):
290 if IsFileUrlString(prefix):
291 completer = self.local_object_completer
292 else:
293 completer = self.cloud_object_completer
294 return completer(prefix, **kwargs)
295
296
297 class NoOpCompleter(object):
298 """Completer that always returns 0 results."""
299
300 def __call__(self, unused_prefix, **unused_kwargs):
301 return []
302
303
304 def MakeCompleter(completer_type, gsutil_api):
305 """Create a completer instance of the given type.
306
307 Args:
308 completer_type: The type of completer to create.
309 gsutil_api: gsutil Cloud API instance to use.
310 Returns:
311 A completer instance.
312 Raises:
313 RuntimeError: if completer type is not supported.
314 """
315 if completer_type == CompleterType.CLOUD_OR_LOCAL_OBJECT:
316 return CloudOrLocalObjectCompleter(gsutil_api)
317 elif completer_type == CompleterType.LOCAL_OBJECT:
318 return LocalObjectCompleter()
319 elif completer_type == CompleterType.LOCAL_OBJECT_OR_CANNED_ACL:
320 return LocalObjectOrCannedACLCompleter()
321 elif completer_type == CompleterType.CLOUD_BUCKET:
322 return CloudObjectCompleter(gsutil_api, bucket_only=True)
323 elif completer_type == CompleterType.CLOUD_OBJECT:
324 return CloudObjectCompleter(gsutil_api)
325 elif completer_type == CompleterType.NO_OP:
326 return NoOpCompleter()
327 else:
328 raise RuntimeError(
329 'Unknown completer "%s"' % completer_type)
330
331
332 def _WriteTimingLog(message):
333 """Write an entry to the tab completion timing log, if it's enabled."""
334 if boto.config.getbool('GSUtil', 'tab_completion_time_logs', False):
335 with open(GetTabCompletionLogFilename(), 'ab') as fp:
336 fp.write(message)
337
OLDNEW
« no previous file with comments | « third_party/gsutil/gslib/storage_url.py ('k') | third_party/gsutil/gslib/tests/__init__.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698