You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

base.py 4.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import os
  2. from io import BytesIO, StringIO, UnsupportedOperation
  3. from django.core.files.utils import FileProxyMixin
  4. from django.utils.functional import cached_property
  5. class File(FileProxyMixin):
  6. DEFAULT_CHUNK_SIZE = 64 * 2 ** 10
  7. def __init__(self, file, name=None):
  8. self.file = file
  9. if name is None:
  10. name = getattr(file, 'name', None)
  11. self.name = name
  12. if hasattr(file, 'mode'):
  13. self.mode = file.mode
  14. def __str__(self):
  15. return self.name or ''
  16. def __repr__(self):
  17. return "<%s: %s>" % (self.__class__.__name__, self or "None")
  18. def __bool__(self):
  19. return bool(self.name)
  20. def __len__(self):
  21. return self.size
  22. @cached_property
  23. def size(self):
  24. if hasattr(self.file, 'size'):
  25. return self.file.size
  26. if hasattr(self.file, 'name'):
  27. try:
  28. return os.path.getsize(self.file.name)
  29. except (OSError, TypeError):
  30. pass
  31. if hasattr(self.file, 'tell') and hasattr(self.file, 'seek'):
  32. pos = self.file.tell()
  33. self.file.seek(0, os.SEEK_END)
  34. size = self.file.tell()
  35. self.file.seek(pos)
  36. return size
  37. raise AttributeError("Unable to determine the file's size.")
  38. def chunks(self, chunk_size=None):
  39. """
  40. Read the file and yield chunks of ``chunk_size`` bytes (defaults to
  41. ``File.DEFAULT_CHUNK_SIZE``).
  42. """
  43. chunk_size = chunk_size or self.DEFAULT_CHUNK_SIZE
  44. try:
  45. self.seek(0)
  46. except (AttributeError, UnsupportedOperation):
  47. pass
  48. while True:
  49. data = self.read(chunk_size)
  50. if not data:
  51. break
  52. yield data
  53. def multiple_chunks(self, chunk_size=None):
  54. """
  55. Return ``True`` if you can expect multiple chunks.
  56. NB: If a particular file representation is in memory, subclasses should
  57. always return ``False`` -- there's no good reason to read from memory in
  58. chunks.
  59. """
  60. return self.size > (chunk_size or self.DEFAULT_CHUNK_SIZE)
  61. def __iter__(self):
  62. # Iterate over this file-like object by newlines
  63. buffer_ = None
  64. for chunk in self.chunks():
  65. for line in chunk.splitlines(True):
  66. if buffer_:
  67. if endswith_cr(buffer_) and not equals_lf(line):
  68. # Line split after a \r newline; yield buffer_.
  69. yield buffer_
  70. # Continue with line.
  71. else:
  72. # Line either split without a newline (line
  73. # continues after buffer_) or with \r\n
  74. # newline (line == b'\n').
  75. line = buffer_ + line
  76. # buffer_ handled, clear it.
  77. buffer_ = None
  78. # If this is the end of a \n or \r\n line, yield.
  79. if endswith_lf(line):
  80. yield line
  81. else:
  82. buffer_ = line
  83. if buffer_ is not None:
  84. yield buffer_
  85. def __enter__(self):
  86. return self
  87. def __exit__(self, exc_type, exc_value, tb):
  88. self.close()
  89. def open(self, mode=None):
  90. if not self.closed:
  91. self.seek(0)
  92. elif self.name and os.path.exists(self.name):
  93. self.file = open(self.name, mode or self.mode)
  94. else:
  95. raise ValueError("The file cannot be reopened.")
  96. return self
  97. def close(self):
  98. self.file.close()
  99. class ContentFile(File):
  100. """
  101. A File-like object that takes just raw content, rather than an actual file.
  102. """
  103. def __init__(self, content, name=None):
  104. stream_class = StringIO if isinstance(content, str) else BytesIO
  105. super().__init__(stream_class(content), name=name)
  106. self.size = len(content)
  107. def __str__(self):
  108. return 'Raw content'
  109. def __bool__(self):
  110. return True
  111. def open(self, mode=None):
  112. self.seek(0)
  113. return self
  114. def close(self):
  115. pass
  116. def write(self, data):
  117. self.__dict__.pop('size', None) # Clear the computed size.
  118. return self.file.write(data)
  119. def endswith_cr(line):
  120. """Return True if line (a text or bytestring) ends with '\r'."""
  121. return line.endswith('\r' if isinstance(line, str) else b'\r')
  122. def endswith_lf(line):
  123. """Return True if line (a text or bytestring) ends with '\n'."""
  124. return line.endswith('\n' if isinstance(line, str) else b'\n')
  125. def equals_lf(line):
  126. """Return True if line (a text or bytestring) equals '\n'."""
  127. return line == ('\n' if isinstance(line, str) else b'\n')