1# Copyright 2013 Google Inc.
2# Copyright 2011, Nexenta Systems Inc.
3#
4# Permission is hereby granted, free of charge, to any person obtaining a
5# copy of this software and associated documentation files (the
6# "Software"), to deal in the Software without restriction, including
7# without limitation the rights to use, copy, modify, merge, publish, dis-
8# tribute, sublicense, and/or sell copies of the Software, and to permit
9# persons to whom the Software is furnished to do so, subject to the fol-
10# lowing conditions:
11#
12# The above copyright notice and this permission notice shall be included
13# in all copies or substantial portions of the Software.
14#
15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
17# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
18# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21# IN THE SOFTWARE.
22
23"""
24Wrapper class to expose a Key being read via a partial implementaiton of the
25Python file interface. The only functions supported are those needed for seeking
26in a Key open for reading.
27"""
28
29import os
30from boto.exception import StorageResponseError
31
32class KeyFile():
33
34  def __init__(self, key):
35    self.key = key
36    self.key.open_read()
37    self.location = 0
38    self.closed = False
39    self.softspace = -1 # Not implemented.
40    self.mode = 'r'
41    self.encoding = 'Undefined in KeyFile'
42    self.errors = 'Undefined in KeyFile'
43    self.newlines = 'Undefined in KeyFile'
44    self.name = key.name
45
46  def tell(self):
47    if self.location is None:
48      raise ValueError("I/O operation on closed file")
49    return self.location
50
51  def seek(self, pos, whence=os.SEEK_SET):
52    self.key.close(fast=True)
53    if whence == os.SEEK_END:
54      # We need special handling for this case because sending an HTTP range GET
55      # with EOF for the range start would cause an invalid range error. Instead
56      # we position to one before EOF (plus pos) and then read one byte to
57      # position at EOF.
58      if self.key.size == 0:
59        # Don't try to seek with an empty key.
60        return
61      pos = self.key.size + pos - 1
62      if pos < 0:
63        raise IOError("Invalid argument")
64      self.key.open_read(headers={"Range": "bytes=%d-" % pos})
65      self.key.read(1)
66      self.location = pos + 1
67      return
68
69    if whence == os.SEEK_SET:
70      if pos < 0:
71        raise IOError("Invalid argument")
72    elif whence == os.SEEK_CUR:
73      pos += self.location
74    else:
75      raise IOError('Invalid whence param (%d) passed to seek' % whence)
76    try:
77      self.key.open_read(headers={"Range": "bytes=%d-" % pos})
78    except StorageResponseError as e:
79      # 416 Invalid Range means that the given starting byte was past the end
80      # of file. We catch this because the Python file interface allows silently
81      # seeking past the end of the file.
82      if e.status != 416:
83        raise
84
85    self.location = pos
86
87  def read(self, size):
88    self.location += size
89    return self.key.read(size)
90
91  def close(self):
92    self.key.close()
93    self.location = None
94    self.closed = True
95
96  def isatty(self):
97    return False
98
99  # Non-file interface, useful for code that wants to dig into underlying Key
100  # state.
101  def getkey(self):
102    return self.key
103
104  # Unimplemented interfaces below here.
105
106  def write(self, buf):
107    raise NotImplementedError('write not implemented in KeyFile')
108
109  def fileno(self):
110    raise NotImplementedError('fileno not implemented in KeyFile')
111
112  def flush(self):
113    raise NotImplementedError('flush not implemented in KeyFile')
114
115  def next(self):
116    raise NotImplementedError('next not implemented in KeyFile')
117
118  def readinto(self):
119    raise NotImplementedError('readinto not implemented in KeyFile')
120
121  def readline(self):
122    raise NotImplementedError('readline not implemented in KeyFile')
123
124  def readlines(self):
125    raise NotImplementedError('readlines not implemented in KeyFile')
126
127  def truncate(self):
128    raise NotImplementedError('truncate not implemented in KeyFile')
129
130  def writelines(self):
131    raise NotImplementedError('writelines not implemented in KeyFile')
132
133  def xreadlines(self):
134    raise NotImplementedError('xreadlines not implemented in KeyFile')
135