Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 
 
 

274 řádky
8.2 KiB

  1. # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. from __future__ import unicode_literals
  4. import frappe
  5. import os, base64, re
  6. import hashlib
  7. import mimetypes
  8. from frappe.utils import get_site_path, get_hook_method, get_files_path, random_string, encode, cstr
  9. from frappe import _
  10. from frappe import conf
  11. from copy import copy
  12. class MaxFileSizeReachedError(frappe.ValidationError): pass
  13. def get_file_url(file_data_name):
  14. data = frappe.db.get_value("File Data", file_data_name, ["file_name", "file_url"], as_dict=True)
  15. return data.file_url or data.file_name
  16. def upload():
  17. # get record details
  18. dt = frappe.form_dict.doctype
  19. dn = frappe.form_dict.docname
  20. file_url = frappe.form_dict.file_url
  21. filename = frappe.form_dict.filename
  22. if not filename and not file_url:
  23. frappe.msgprint(_("Please select a file or url"),
  24. raise_exception=True)
  25. # save
  26. if filename:
  27. filedata = save_uploaded(dt, dn)
  28. elif file_url:
  29. filedata = save_url(file_url, dt, dn)
  30. if dt and dn:
  31. comment = frappe.get_doc(dt, dn).add_comment("Attachment",
  32. _("Added {0}").format("<a href='{file_url}' target='_blank'>{file_name}</a>".format(**filedata.as_dict())))
  33. return {
  34. "name": filedata.name,
  35. "file_name": filedata.file_name,
  36. "file_url": filedata.file_url,
  37. "comment": comment.as_dict()
  38. }
  39. def save_uploaded(dt, dn):
  40. fname, content = get_uploaded_content()
  41. if content:
  42. return save_file(fname, content, dt, dn);
  43. else:
  44. raise Exception
  45. def save_url(file_url, dt, dn):
  46. # if not (file_url.startswith("http://") or file_url.startswith("https://")):
  47. # frappe.msgprint("URL must start with 'http://' or 'https://'")
  48. # return None, None
  49. f = frappe.get_doc({
  50. "doctype": "File Data",
  51. "file_url": file_url,
  52. "attached_to_doctype": dt,
  53. "attached_to_name": dn
  54. })
  55. f.ignore_permissions = True
  56. try:
  57. f.insert();
  58. except frappe.DuplicateEntryError:
  59. return frappe.get_doc("File Data", f.duplicate_entry)
  60. return f
  61. def get_uploaded_content():
  62. # should not be unicode when reading a file, hence using frappe.form
  63. if 'filedata' in frappe.form_dict:
  64. if "," in frappe.form_dict.filedata:
  65. frappe.form_dict.filedata = frappe.form_dict.filedata.rsplit(",", 1)[1]
  66. frappe.uploaded_content = base64.b64decode(frappe.form_dict.filedata)
  67. frappe.uploaded_filename = frappe.form_dict.filename
  68. return frappe.uploaded_filename, frappe.uploaded_content
  69. else:
  70. frappe.msgprint(_('No file attached'))
  71. return None, None
  72. def extract_images_from_html(doc, fieldname):
  73. content = doc.get(fieldname)
  74. frappe.flags.has_dataurl = False
  75. def _save_file(match):
  76. data = match.group(1)
  77. data = data.split("data:")[1]
  78. headers, content = data.split(",")
  79. if "filename=" in headers:
  80. filename = headers.split("filename=")[-1]
  81. else:
  82. mtype = headers.split(";")[0]
  83. extn = mimetypes.guess_extension(mtype)
  84. filename = random_string(7) + extn
  85. # TODO fix this
  86. file_url = save_file(filename, content, doc.doctype, doc.name, decode=True).get("file_url")
  87. if not frappe.flags.has_dataurl:
  88. frappe.flags.has_dataurl = True
  89. return '<img src="{file_url}"'.format(file_url=file_url)
  90. if content:
  91. content = re.sub('<img\s*src=\s*["\'](?=data:)(.*?)["\']', _save_file, content)
  92. if frappe.flags.has_dataurl:
  93. doc.set(fieldname, content)
  94. def save_file(fname, content, dt, dn, decode=False):
  95. if decode:
  96. if isinstance(content, unicode):
  97. content = content.encode("utf-8")
  98. if "," in content:
  99. content = content.split(",")[1]
  100. content = base64.b64decode(content)
  101. file_size = check_max_file_size(content)
  102. content_hash = get_content_hash(content)
  103. content_type = mimetypes.guess_type(fname)[0]
  104. fname = get_file_name(fname, content_hash[-6:])
  105. file_data = get_file_data_from_hash(content_hash)
  106. if not file_data:
  107. method = get_hook_method('write_file', fallback=save_file_on_filesystem)
  108. file_data = method(fname, content, content_type=content_type)
  109. file_data = copy(file_data)
  110. file_data.update({
  111. "doctype": "File Data",
  112. "attached_to_doctype": dt,
  113. "attached_to_name": dn,
  114. "file_size": file_size,
  115. "content_hash": content_hash,
  116. })
  117. f = frappe.get_doc(file_data)
  118. f.ignore_permissions = True
  119. try:
  120. f.insert();
  121. except frappe.DuplicateEntryError:
  122. return frappe.get_doc("File Data", f.duplicate_entry)
  123. return f
  124. def get_file_data_from_hash(content_hash):
  125. for name in frappe.db.sql_list("select name from `tabFile Data` where content_hash=%s", content_hash):
  126. b = frappe.get_doc('File Data', name)
  127. return {k:b.get(k) for k in frappe.get_hooks()['write_file_keys']}
  128. return False
  129. def save_file_on_filesystem(fname, content, content_type=None):
  130. public_path = os.path.join(frappe.local.site_path, "public")
  131. fpath = write_file(content, get_files_path(), fname)
  132. path = os.path.relpath(fpath, public_path)
  133. return {
  134. 'file_name': os.path.basename(path),
  135. 'file_url': '/' + path
  136. }
  137. def check_max_file_size(content):
  138. max_file_size = conf.get('max_file_size') or 3145728
  139. file_size = len(content)
  140. if file_size > max_file_size:
  141. frappe.msgprint(_("File size exceeded the maximum allowed size of {0} MB").format(
  142. max_file_size / 1048576),
  143. raise_exception=MaxFileSizeReachedError)
  144. return file_size
  145. def write_file(content, file_path, fname):
  146. """write file to disk with a random name (to compare)"""
  147. # create directory (if not exists)
  148. frappe.create_folder(get_files_path())
  149. # write the file
  150. with open(os.path.join(file_path.encode('utf-8'), fname.encode('utf-8')), 'w+') as f:
  151. f.write(content)
  152. return get_files_path(fname)
  153. def remove_all(dt, dn):
  154. """remove all files in a transaction"""
  155. try:
  156. for fid in frappe.db.sql_list("""select name from `tabFile Data` where
  157. attached_to_doctype=%s and attached_to_name=%s""", (dt, dn)):
  158. remove_file(fid, dt, dn)
  159. except Exception, e:
  160. if e.args[0]!=1054: raise # (temp till for patched)
  161. def remove_file_by_url(file_url, doctype=None, name=None):
  162. if doctype and name:
  163. fid = frappe.db.get_value("File Data", {"file_url": file_url,
  164. "attached_to_doctype": doctype, "attached_to_name": name})
  165. else:
  166. fid = frappe.db.get_value("File Data", {"file_url": file_url})
  167. if fid:
  168. return remove_file(fid)
  169. def remove_file(fid, attached_to_doctype=None, attached_to_name=None):
  170. """Remove file and File Data entry"""
  171. file_name = None
  172. if not (attached_to_doctype and attached_to_name):
  173. attached = frappe.db.get_value("File Data", fid,
  174. ["attached_to_doctype", "attached_to_name", "file_name"])
  175. if attached:
  176. attached_to_doctype, attached_to_name, file_name = attached
  177. ignore_permissions, comment = False, None
  178. if attached_to_doctype and attached_to_name:
  179. doc = frappe.get_doc(attached_to_doctype, attached_to_name)
  180. ignore_permissions = doc.has_permission("write") or False
  181. if not file_name:
  182. file_name = frappe.db.get_value("File Data", fid, "file_name")
  183. comment = doc.add_comment("Attachment Removed", _("Removed {0}").format(file_name))
  184. frappe.delete_doc("File Data", fid, ignore_permissions=ignore_permissions)
  185. return comment
  186. def delete_file_data_content(doc):
  187. method = get_hook_method('delete_file_data_content', fallback=delete_file_from_filesystem)
  188. method(doc)
  189. def delete_file_from_filesystem(doc):
  190. path = doc.file_name
  191. if path.startswith("files/"):
  192. path = frappe.utils.get_site_path("public", doc.file_name)
  193. else:
  194. path = frappe.utils.get_site_path("public", "files", doc.file_name)
  195. if os.path.exists(path):
  196. os.remove(path)
  197. def get_file(fname):
  198. f = frappe.db.sql("""select file_name from `tabFile Data`
  199. where name=%s or file_name=%s""", (fname, fname))
  200. if f:
  201. file_name = f[0][0]
  202. else:
  203. file_name = fname
  204. file_path = file_name
  205. if not "/" in file_path:
  206. file_path = "files/" + file_path
  207. # read the file
  208. with open(get_site_path("public", file_path), 'r') as f:
  209. content = f.read()
  210. return [file_name, content]
  211. def get_content_hash(content):
  212. return hashlib.md5(content).hexdigest()
  213. def get_file_name(fname, optional_suffix):
  214. # convert to unicode
  215. fname = cstr(fname)
  216. n_records = frappe.db.sql("select name from `tabFile Data` where file_name=%s", fname)
  217. if len(n_records) > 0 or os.path.exists(encode(get_files_path(fname))):
  218. f = fname.rsplit('.', 1)
  219. if len(f) == 1:
  220. partial, extn = f[0], ""
  221. else:
  222. partial, extn = f[0], "." + f[1]
  223. return '{partial}{suffix}{extn}'.format(partial=partial, extn=extn, suffix=optional_suffix)
  224. return fname