2008-08-09 04:59:02 +08:00
|
|
|
import os
|
2012-08-29 15:45:02 +08:00
|
|
|
from io import BytesIO, StringIO, UnsupportedOperation
|
2008-08-09 04:59:02 +08:00
|
|
|
|
2009-05-08 23:08:09 +08:00
|
|
|
from django.core.files.utils import FileProxyMixin
|
|
|
|
|
2013-11-03 04:12:09 +08:00
|
|
|
|
2009-05-08 23:08:09 +08:00
|
|
|
class File(FileProxyMixin):
|
2013-11-04 02:08:55 +08:00
|
|
|
DEFAULT_CHUNK_SIZE = 64 * 2 ** 10
|
2008-08-09 04:59:02 +08:00
|
|
|
|
2009-05-08 23:08:09 +08:00
|
|
|
def __init__(self, file, name=None):
|
2008-08-09 04:59:02 +08:00
|
|
|
self.file = file
|
2009-05-08 23:08:09 +08:00
|
|
|
if name is None:
|
|
|
|
name = getattr(file, 'name', None)
|
|
|
|
self.name = name
|
2012-05-31 15:59:58 +08:00
|
|
|
if hasattr(file, 'mode'):
|
|
|
|
self.mode = file.mode
|
2008-08-09 04:59:02 +08:00
|
|
|
|
2012-08-12 18:32:08 +08:00
|
|
|
def __str__(self):
|
2017-03-04 22:47:49 +08:00
|
|
|
return self.name or ''
|
2008-08-09 04:59:02 +08:00
|
|
|
|
|
|
|
def __repr__(self):
|
2017-01-12 06:17:25 +08:00
|
|
|
return "<%s: %s>" % (self.__class__.__name__, self or "None")
|
2008-08-09 04:59:02 +08:00
|
|
|
|
2012-08-08 20:52:21 +08:00
|
|
|
def __bool__(self):
|
2009-05-08 23:08:09 +08:00
|
|
|
return bool(self.name)
|
2012-11-04 04:43:11 +08:00
|
|
|
|
2008-08-09 04:59:02 +08:00
|
|
|
def __len__(self):
|
|
|
|
return self.size
|
|
|
|
|
2014-03-23 00:12:43 +08:00
|
|
|
def _get_size_from_underlying_file(self):
|
|
|
|
if hasattr(self.file, 'size'):
|
|
|
|
return self.file.size
|
|
|
|
if hasattr(self.file, 'name'):
|
|
|
|
try:
|
|
|
|
return os.path.getsize(self.file.name)
|
|
|
|
except (OSError, TypeError):
|
|
|
|
pass
|
|
|
|
if hasattr(self.file, 'tell') and hasattr(self.file, 'seek'):
|
|
|
|
pos = self.file.tell()
|
|
|
|
self.file.seek(0, os.SEEK_END)
|
|
|
|
size = self.file.tell()
|
|
|
|
self.file.seek(pos)
|
|
|
|
return size
|
|
|
|
raise AttributeError("Unable to determine the file's size.")
|
|
|
|
|
2008-08-09 04:59:02 +08:00
|
|
|
def _get_size(self):
|
2014-03-23 00:12:43 +08:00
|
|
|
if hasattr(self, '_size'):
|
|
|
|
return self._size
|
|
|
|
self._size = self._get_size_from_underlying_file()
|
2008-08-09 04:59:02 +08:00
|
|
|
return self._size
|
|
|
|
|
|
|
|
def _set_size(self, size):
|
|
|
|
self._size = size
|
|
|
|
|
|
|
|
size = property(_get_size, _set_size)
|
|
|
|
|
|
|
|
def chunks(self, chunk_size=None):
|
|
|
|
"""
|
2015-04-03 07:27:59 +08:00
|
|
|
Read the file and yield chunks of ``chunk_size`` bytes (defaults to
|
2008-08-09 04:59:02 +08:00
|
|
|
``UploadedFile.DEFAULT_CHUNK_SIZE``).
|
|
|
|
"""
|
2018-01-04 07:52:12 +08:00
|
|
|
chunk_size = chunk_size or self.DEFAULT_CHUNK_SIZE
|
2012-08-18 16:24:23 +08:00
|
|
|
try:
|
2008-08-09 04:59:02 +08:00
|
|
|
self.seek(0)
|
2012-08-18 16:24:23 +08:00
|
|
|
except (AttributeError, UnsupportedOperation):
|
|
|
|
pass
|
2008-08-09 04:59:02 +08:00
|
|
|
|
2012-04-05 23:44:04 +08:00
|
|
|
while True:
|
|
|
|
data = self.read(chunk_size)
|
|
|
|
if not data:
|
|
|
|
break
|
|
|
|
yield data
|
2008-08-09 04:59:02 +08:00
|
|
|
|
|
|
|
def multiple_chunks(self, chunk_size=None):
|
|
|
|
"""
|
2017-01-26 03:02:33 +08:00
|
|
|
Return ``True`` if you can expect multiple chunks.
|
2008-08-09 04:59:02 +08:00
|
|
|
|
|
|
|
NB: If a particular file representation is in memory, subclasses should
|
|
|
|
always return ``False`` -- there's no good reason to read from memory in
|
|
|
|
chunks.
|
|
|
|
"""
|
2018-01-04 07:52:12 +08:00
|
|
|
return self.size > (chunk_size or self.DEFAULT_CHUNK_SIZE)
|
2008-08-09 04:59:02 +08:00
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
# Iterate over this file-like object by newlines
|
|
|
|
buffer_ = None
|
|
|
|
for chunk in self.chunks():
|
2014-09-30 09:24:33 +08:00
|
|
|
for line in chunk.splitlines(True):
|
2008-08-09 04:59:02 +08:00
|
|
|
if buffer_:
|
2014-09-30 09:24:33 +08:00
|
|
|
if endswith_cr(buffer_) and not equals_lf(line):
|
|
|
|
# Line split after a \r newline; yield buffer_.
|
|
|
|
yield buffer_
|
|
|
|
# Continue with line.
|
|
|
|
else:
|
|
|
|
# Line either split without a newline (line
|
|
|
|
# continues after buffer_) or with \r\n
|
|
|
|
# newline (line == b'\n').
|
|
|
|
line = buffer_ + line
|
|
|
|
# buffer_ handled, clear it.
|
2008-08-09 04:59:02 +08:00
|
|
|
buffer_ = None
|
|
|
|
|
2014-09-30 09:24:33 +08:00
|
|
|
# If this is the end of a \n or \r\n line, yield.
|
|
|
|
if endswith_lf(line):
|
2008-08-09 04:59:02 +08:00
|
|
|
yield line
|
|
|
|
else:
|
|
|
|
buffer_ = line
|
|
|
|
|
|
|
|
if buffer_ is not None:
|
|
|
|
yield buffer_
|
|
|
|
|
2010-11-22 01:51:41 +08:00
|
|
|
def __enter__(self):
|
|
|
|
return self
|
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc_value, tb):
|
|
|
|
self.close()
|
|
|
|
|
2008-08-09 04:59:02 +08:00
|
|
|
def open(self, mode=None):
|
|
|
|
if not self.closed:
|
|
|
|
self.seek(0)
|
2009-05-11 17:57:19 +08:00
|
|
|
elif self.name and os.path.exists(self.name):
|
|
|
|
self.file = open(self.name, mode or self.mode)
|
2008-08-09 04:59:02 +08:00
|
|
|
else:
|
|
|
|
raise ValueError("The file cannot be reopened.")
|
2017-04-07 20:21:06 +08:00
|
|
|
return self
|
2008-08-09 04:59:02 +08:00
|
|
|
|
|
|
|
def close(self):
|
|
|
|
self.file.close()
|
|
|
|
|
2013-11-03 04:12:09 +08:00
|
|
|
|
2008-08-09 04:59:02 +08:00
|
|
|
class ContentFile(File):
|
|
|
|
"""
|
2017-01-26 03:02:33 +08:00
|
|
|
A File-like object that take just raw content, rather than an actual file.
|
2008-08-09 04:59:02 +08:00
|
|
|
"""
|
2011-12-30 22:51:05 +08:00
|
|
|
def __init__(self, content, name=None):
|
2016-12-29 23:27:49 +08:00
|
|
|
stream_class = StringIO if isinstance(content, str) else BytesIO
|
2017-01-21 21:13:44 +08:00
|
|
|
super().__init__(stream_class(content), name=name)
|
2009-05-08 23:08:09 +08:00
|
|
|
self.size = len(content)
|
2008-08-09 04:59:02 +08:00
|
|
|
|
2012-08-12 18:32:08 +08:00
|
|
|
def __str__(self):
|
2008-08-09 04:59:02 +08:00
|
|
|
return 'Raw content'
|
|
|
|
|
2012-08-08 20:52:21 +08:00
|
|
|
def __bool__(self):
|
2008-08-09 04:59:02 +08:00
|
|
|
return True
|
2012-11-04 04:43:11 +08:00
|
|
|
|
2008-08-09 04:59:02 +08:00
|
|
|
def open(self, mode=None):
|
|
|
|
self.seek(0)
|
2017-04-07 20:21:06 +08:00
|
|
|
return self
|
2009-05-11 17:57:19 +08:00
|
|
|
|
|
|
|
def close(self):
|
|
|
|
pass
|
2014-09-30 09:24:33 +08:00
|
|
|
|
2018-03-08 05:20:25 +08:00
|
|
|
def write(self, data):
|
|
|
|
self.__dict__.pop('_size', None) # Clear the computed size.
|
|
|
|
return self.file.write(data)
|
|
|
|
|
2014-09-30 09:24:33 +08:00
|
|
|
|
|
|
|
def endswith_cr(line):
|
2017-01-26 03:02:33 +08:00
|
|
|
"""Return True if line (a text or byte string) ends with '\r'."""
|
2016-12-29 23:27:49 +08:00
|
|
|
return line.endswith('\r' if isinstance(line, str) else b'\r')
|
2014-09-30 09:24:33 +08:00
|
|
|
|
|
|
|
|
|
|
|
def endswith_lf(line):
|
2017-01-26 03:02:33 +08:00
|
|
|
"""Return True if line (a text or byte string) ends with '\n'."""
|
2016-12-29 23:27:49 +08:00
|
|
|
return line.endswith('\n' if isinstance(line, str) else b'\n')
|
2014-09-30 09:24:33 +08:00
|
|
|
|
|
|
|
|
|
|
|
def equals_lf(line):
|
2017-01-26 03:02:33 +08:00
|
|
|
"""Return True if line (a text or byte string) equals '\n'."""
|
2016-12-29 23:27:49 +08:00
|
|
|
return line == ('\n' if isinstance(line, str) else b'\n')
|