OLD | NEW |
(Empty) | |
| 1 from __future__ import unicode_literals |
| 2 |
| 3 import os |
| 4 from io import BytesIO, StringIO, UnsupportedOperation |
| 5 |
| 6 from django.core.files.utils import FileProxyMixin |
| 7 from django.utils import six |
| 8 from django.utils.encoding import ( |
| 9 force_bytes, force_str, force_text, python_2_unicode_compatible, |
| 10 ) |
| 11 |
| 12 |
| 13 @python_2_unicode_compatible |
| 14 class File(FileProxyMixin): |
| 15 DEFAULT_CHUNK_SIZE = 64 * 2 ** 10 |
| 16 |
| 17 def __init__(self, file, name=None): |
| 18 self.file = file |
| 19 if name is None: |
| 20 name = getattr(file, 'name', None) |
| 21 self.name = name |
| 22 if hasattr(file, 'mode'): |
| 23 self.mode = file.mode |
| 24 |
| 25 def __str__(self): |
| 26 return force_text(self.name or '') |
| 27 |
| 28 def __repr__(self): |
| 29 return force_str("<%s: %s>" % (self.__class__.__name__, self or "None")) |
| 30 |
| 31 def __bool__(self): |
| 32 return bool(self.name) |
| 33 |
| 34 def __nonzero__(self): # Python 2 compatibility |
| 35 return type(self).__bool__(self) |
| 36 |
| 37 def __len__(self): |
| 38 return self.size |
| 39 |
| 40 def _get_size_from_underlying_file(self): |
| 41 if hasattr(self.file, 'size'): |
| 42 return self.file.size |
| 43 if hasattr(self.file, 'name'): |
| 44 try: |
| 45 return os.path.getsize(self.file.name) |
| 46 except (OSError, TypeError): |
| 47 pass |
| 48 if hasattr(self.file, 'tell') and hasattr(self.file, 'seek'): |
| 49 pos = self.file.tell() |
| 50 self.file.seek(0, os.SEEK_END) |
| 51 size = self.file.tell() |
| 52 self.file.seek(pos) |
| 53 return size |
| 54 raise AttributeError("Unable to determine the file's size.") |
| 55 |
| 56 def _get_size(self): |
| 57 if hasattr(self, '_size'): |
| 58 return self._size |
| 59 self._size = self._get_size_from_underlying_file() |
| 60 return self._size |
| 61 |
| 62 def _set_size(self, size): |
| 63 self._size = size |
| 64 |
| 65 size = property(_get_size, _set_size) |
| 66 |
| 67 def chunks(self, chunk_size=None): |
| 68 """ |
| 69 Read the file and yield chunks of ``chunk_size`` bytes (defaults to |
| 70 ``UploadedFile.DEFAULT_CHUNK_SIZE``). |
| 71 """ |
| 72 if not chunk_size: |
| 73 chunk_size = self.DEFAULT_CHUNK_SIZE |
| 74 |
| 75 try: |
| 76 self.seek(0) |
| 77 except (AttributeError, UnsupportedOperation): |
| 78 pass |
| 79 |
| 80 while True: |
| 81 data = self.read(chunk_size) |
| 82 if not data: |
| 83 break |
| 84 yield data |
| 85 |
| 86 def multiple_chunks(self, chunk_size=None): |
| 87 """ |
| 88 Returns ``True`` if you can expect multiple chunks. |
| 89 |
| 90 NB: If a particular file representation is in memory, subclasses should |
| 91 always return ``False`` -- there's no good reason to read from memory in |
| 92 chunks. |
| 93 """ |
| 94 if not chunk_size: |
| 95 chunk_size = self.DEFAULT_CHUNK_SIZE |
| 96 return self.size > chunk_size |
| 97 |
| 98 def __iter__(self): |
| 99 # Iterate over this file-like object by newlines |
| 100 buffer_ = None |
| 101 for chunk in self.chunks(): |
| 102 for line in chunk.splitlines(True): |
| 103 if buffer_: |
| 104 if endswith_cr(buffer_) and not equals_lf(line): |
| 105 # Line split after a \r newline; yield buffer_. |
| 106 yield buffer_ |
| 107 # Continue with line. |
| 108 else: |
| 109 # Line either split without a newline (line |
| 110 # continues after buffer_) or with \r\n |
| 111 # newline (line == b'\n'). |
| 112 line = buffer_ + line |
| 113 # buffer_ handled, clear it. |
| 114 buffer_ = None |
| 115 |
| 116 # If this is the end of a \n or \r\n line, yield. |
| 117 if endswith_lf(line): |
| 118 yield line |
| 119 else: |
| 120 buffer_ = line |
| 121 |
| 122 if buffer_ is not None: |
| 123 yield buffer_ |
| 124 |
| 125 def __enter__(self): |
| 126 return self |
| 127 |
| 128 def __exit__(self, exc_type, exc_value, tb): |
| 129 self.close() |
| 130 |
| 131 def open(self, mode=None): |
| 132 if not self.closed: |
| 133 self.seek(0) |
| 134 elif self.name and os.path.exists(self.name): |
| 135 self.file = open(self.name, mode or self.mode) |
| 136 else: |
| 137 raise ValueError("The file cannot be reopened.") |
| 138 |
| 139 def close(self): |
| 140 self.file.close() |
| 141 |
| 142 |
| 143 @python_2_unicode_compatible |
| 144 class ContentFile(File): |
| 145 """ |
| 146 A File-like object that takes just raw content, rather than an actual file. |
| 147 """ |
| 148 def __init__(self, content, name=None): |
| 149 if six.PY3: |
| 150 stream_class = StringIO if isinstance(content, six.text_type) else B
ytesIO |
| 151 else: |
| 152 stream_class = BytesIO |
| 153 content = force_bytes(content) |
| 154 super(ContentFile, self).__init__(stream_class(content), name=name) |
| 155 self.size = len(content) |
| 156 |
| 157 def __str__(self): |
| 158 return 'Raw content' |
| 159 |
| 160 def __bool__(self): |
| 161 return True |
| 162 |
| 163 def __nonzero__(self): # Python 2 compatibility |
| 164 return type(self).__bool__(self) |
| 165 |
| 166 def open(self, mode=None): |
| 167 self.seek(0) |
| 168 |
| 169 def close(self): |
| 170 pass |
| 171 |
| 172 |
| 173 def endswith_cr(line): |
| 174 """ |
| 175 Return True if line (a text or byte string) ends with '\r'. |
| 176 """ |
| 177 return line.endswith('\r' if isinstance(line, six.text_type) else b'\r') |
| 178 |
| 179 |
| 180 def endswith_lf(line): |
| 181 """ |
| 182 Return True if line (a text or byte string) ends with '\n'. |
| 183 """ |
| 184 return line.endswith('\n' if isinstance(line, six.text_type) else b'\n') |
| 185 |
| 186 |
| 187 def equals_lf(line): |
| 188 """ |
| 189 Return True if line (a text or byte string) equals '\n'. |
| 190 """ |
| 191 return line == ('\n' if isinstance(line, six.text_type) else b'\n') |
OLD | NEW |