diff --git a/frappe/client.py b/frappe/client.py index 454b0a79e1..0be029599a 100644 --- a/frappe/client.py +++ b/frappe/client.py @@ -9,7 +9,6 @@ import frappe.utils import json, os from six import iteritems, string_types, integer_types -from frappe.utils.file_manager import save_file ''' Handle RESTful requests that are mapped to the `/api/resource` route. @@ -351,10 +350,20 @@ def attach_file(filename=None, filedata=None, doctype=None, docname=None, folder if not doc.has_permission(): frappe.throw(_("Not permitted"), frappe.PermissionError) - f = save_file(filename, filedata, doctype, docname, folder, decode_base64, is_private, docfield) + _file = frappe.get_doc({ + "doctype": "File", + "file_name": filename, + "attached_to_doctype": doctype, + "attached_to_name": docname, + "attached_to_field": docfield, + "folder": folder, + "is_private": is_private, + "content": filedata, + "decode": decode_base64}) + _file.save() if docfield and doctype: - doc.set(docfield, f.file_url) + doc.set(docfield, _file.file_url) doc.save() return f.as_dict() diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index d84257a5fc..48c8dfcae1 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -10,7 +10,6 @@ from email.utils import formataddr from frappe.core.utils import get_parent_doc from frappe.utils import (get_url, get_formatted_email, cint, validate_email_add, split_emails, time_diff_in_seconds, parse_addr, get_datetime) -from frappe.utils.file_manager import get_file from frappe.email.queue import check_email_limit from frappe.utils.scheduler import log from frappe.email.email_body import get_message_id @@ -281,7 +280,8 @@ def prepare_to_notify(doc, print_html=None, print_format=None, attachments=None) # is it a filename? try: # keep this for error handling - file = get_file(a) + _file = frappe.get_doc("File", {"file_name": a}) + _file.get_content() # these attachments will be attached on-demand # and won't be stored in the message doc.attachments.append({"fid": a}) @@ -393,8 +393,6 @@ def get_bcc(doc, recipients=None, fetched_from_email_account=False): def add_attachments(name, attachments): '''Add attachments to the given Communiction''' - from frappe.utils.file_manager import save_url - # loop through attachments for a in attachments: if isinstance(a, string_types): @@ -402,8 +400,14 @@ def add_attachments(name, attachments): ["file_name", "file_url", "is_private"], as_dict=1) # save attachments to new doc - save_url(attach.file_url, attach.file_name, "Communication", name, - "Home/Attachments", attach.is_private) + _file = frappe.get_doc({ + "doctype": "File", + "file_url": attach.file_url, + "attached_to_doctype": "Communication", + "attached_to_name": name, + "folder": "Home/Attachments"}) + _file.save() + def filter_email_list(doc, email_list, exclude, is_cc=False, is_bcc=False): # temp variables diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py index 299eb1de9d..4a3eb498e0 100644 --- a/frappe/core/doctype/data_import/importer.py +++ b/frappe/core/doctype/data_import/importer.py @@ -12,7 +12,6 @@ from frappe import _ from frappe.utils.csvutils import getlink from frappe.utils.dateutils import parse_date -from frappe.utils.file_manager import save_url from frappe.utils import cint, cstr, flt, getdate, get_datetime, get_url, get_url_to_form from six import text_type, string_types @@ -265,14 +264,22 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, # file is already attached return - save_url(file_url, None, doctype, docname, "Home/Attachments", 0) + _file = frappe.get_doc({ + "doctype": "File", + "file_url": file_url, + "attached_to_name": docname, + "attached_to_doctype": doctype, + "attached_to_field": 0, + "folder": "Home/Attachments"}) + _file.save() + # header filename, file_extension = ['',''] if not rows: - from frappe.utils.file_manager import get_file # get_file_doc - fname, fcontent = get_file(data_import_doc.import_file) - filename, file_extension = os.path.splitext(fname) + _file = frappe.get_doc("File", {"file_url": data_import_doc.import_file}) + fcontent = _file.get_content() + filename, file_extension = _file.get_extension() if file_extension == '.xlsx' and from_data_import == 'Yes': from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file @@ -455,7 +462,6 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, if error_flag and data_import_doc.skip_errors and len(data) != len(data_rows_with_error): import_status = "Partially Successful" # write the file with the faulty row - from frappe.utils.file_manager import save_file file_name = 'error_' + filename + file_extension if file_extension == '.xlsx': from frappe.utils.xlsxutils import make_xlsx @@ -464,9 +470,15 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, else: from frappe.utils.csvutils import to_csv file_data = to_csv(data_rows_with_error) - error_data_file = save_file(file_name, file_data, "Data Import", - data_import_doc.name, "Home/Attachments") - data_import_doc.error_file = error_data_file.file_url + _file = frappe.get_doc({ + "doctype": "File", + "file_name": file_name, + "attached_to_doctype": "Data Import", + "attached_to_name": data_import_doc.name, + "folder": "Home/Attachments", + "content": file_data}) + _file.save() + data_import_doc.error_file = _file.file_url elif error_flag: import_status = "Failed" diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index ed48cef010..bc6767771a 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -11,24 +11,36 @@ naming for same name files: file.gif, file-1.gif, file-2.gif etc import frappe import json import os +import base64 +import re +import hashlib +import mimetypes +import io import shutil import requests import requests.exceptions -import mimetypes, imghdr +import imghdr -from frappe.utils.file_manager import delete_file_data_content, get_content_hash, get_random_filename +from frappe.utils import get_hook_method, get_files_path, random_string, encode, cstr, call_hook_method, cint from frappe import _ +from frappe import conf from frappe.utils.nestedset import NestedSet -from frappe.utils import strip, get_files_path +from frappe.utils import strip from PIL import Image, ImageOps from six import StringIO, string_types from six.moves.urllib.parse import unquote +from six import text_type, PY2 import zipfile +class MaxFileSizeReachedError(frappe.ValidationError): + pass + + class FolderNotEmpty(frappe.ValidationError): pass exclude_from_linked_with = True + class File(NestedSet): nsm_parent_field = 'folder' no_feed_on_delete = True @@ -36,6 +48,10 @@ class File(NestedSet): def before_insert(self): frappe.local.rollback_observers.append(self) self.set_folder_name() + self.content = self.get("content", None) + self.decode = self.get("decode", False) + if self.content: + self.save_file(content=self.content, decode=self.decode) def get_name_based_on_parent_folder(self): path = get_breadcrumbs(self.folder) @@ -69,10 +85,12 @@ class File(NestedSet): self.validate_folder() if not self.flags.ignore_file_validate: - self.validate_file() + if not self.is_folder: + self.validate_file() self.generate_content_hash() self.set_folder_size() + self.validate_url() if frappe.db.exists('File', {'name': self.name, 'is_folder': 0}): old_file_url = self.file_url @@ -104,7 +122,8 @@ class File(NestedSet): break self.attached_to_field = field_name if self.attached_to_field: - frappe.db.set_value(self.attached_to_doctype, self.attached_to_name, self.attached_to_field, self.file_url) + frappe.db.set_value(self.attached_to_doctype, self.attached_to_name, + self.attached_to_field, self.file_url) def set_folder_size(self): @@ -139,18 +158,16 @@ class File(NestedSet): def validate_folder(self): if not self.is_home_folder and not self.folder and \ not self.flags.ignore_folder_validate: - frappe.throw(_("Folder is mandatory")) + self.folder = "Home" def validate_file(self): """Validates existence of public file TODO: validate for private file """ - if (self.file_url or "").startswith("/files/"): - if not self.file_name: - self.file_name = self.file_url.split("/files/")[-1] + full_path = self.get_full_path() - if not os.path.exists(get_files_path(frappe.as_unicode(self.file_name.lstrip("/")))): - frappe.throw(_("File {0} does not exist").format(self.file_url), IOError) + if not os.path.exists(full_path): + frappe.throw(_("File {0} does not exist").format(self.file_url), IOError) def validate_duplicate_entry(self): if not self.flags.ignore_duplicate_entry_error and not self.is_folder: @@ -165,7 +182,8 @@ class File(NestedSet): self.attached_to_name)) if len(n_records) > 0: self.duplicate_entry = n_records[0][0] - frappe.throw(frappe._("Same file has already been attached to the record"), frappe.DuplicateEntryError) + frappe.throw(frappe._("Same file has already been attached to the record"), + frappe.DuplicateEntryError) def generate_content_hash(self): if self.content_hash or not self.file_url: @@ -185,7 +203,7 @@ class File(NestedSet): self.check_folder_is_empty() self.check_reference_doc_permission() super(File, self).on_trash() - self.delete_file() + self.call_delete_file() def make_thumbnail(self, set_as_thumbnail=True, width=300, height=300, suffix="small", crop=False): if self.file_url: @@ -246,19 +264,20 @@ class File(NestedSet): if not self.flags.ignore_permissions and \ not frappe.has_permission(self.attached_to_doctype, "write", self.attached_to_name): - frappe.throw(frappe._("Cannot delete file as it belongs to {0} {1} for which you do not have permissions").format(self.attached_to_doctype, self.attached_to_name), + frappe.throw(frappe._( + "Cannot delete file as it belongs to {0} {1} for which you do not have permissions").format( + self.attached_to_doctype, self.attached_to_name), frappe.PermissionError) except frappe.DoesNotExistError: pass - def delete_file(self): + def call_delete_file(self): """If file not attached to any other record, delete it""" if self.file_name and self.content_hash and (not frappe.db.count("File", {"content_hash": self.content_hash, "name": ["!=", self.name]})): - delete_file_data_content(self) - + self.delete_file_data_content() elif self.file_url: - delete_file_data_content(self, only_thumbnail=True) + self.delete_file_data_content(only_thumbnail=True) def on_rollback(self): self.flags.on_rollback = True @@ -293,6 +312,253 @@ class File(NestedSet): frappe.delete_doc('File', self.name) + + def get_file_url(self): + data = frappe.db.get_value("File", self.file_data_name, ["file_name", "file_url"], as_dict=True) + return data.file_url or data.file_name + + + def upload(self): + # get record details + self.attached_to_doctype = frappe.form_dict.doctype + self.attached_to_name = frappe.form_dict.docname + self.attached_to_field = frappe.form_dict.docfield + self.file_url = frappe.form_dict.file_url + self.file_name = frappe.form_dict.filename + frappe.form_dict.is_private = cint(frappe.form_dict.is_private) + + if not self.file_name and not self.file_url: + frappe.msgprint(_("Please select a file or url"), + raise_exception=True) + + file_doc = self.get_file_doc() + + comment = {} + if self.attached_to_doctype and self.attached_to_name: + comment = frappe.get_doc(self.attached_to_doctype, self.attached_to_name).add_comment("Attachment", + _ ("added {0}").format("{file_name}{icon}".format(**{ + "icon": ' ' \ + if file_doc.is_private else "", + "file_url": file_doc.file_url.replace("#", "%23") \ + if file_doc.file_name else file_doc.file_url, + "file_name": file_doc.file_name or file_doc.file_url + }))) + + return { + "name": file_doc.name, + "file_name": file_doc.file_name, + "file_url": file_doc.file_url, + "is_private": file_doc.is_private, + "comment": comment.as_dict() if comment else {} + } + + def get_content(self): + """Returns [`file_name`, `content`] for given file name `fname`""" + if self.get('content'): + return self.content + file_path = self.get_full_path() + + # read the file + if PY2: + with open(encode(file_path)) as f: + content = f.read() + else: + with io.open(encode(file_path), mode='rb') as f: + content = f.read() + try: + # for plain text files + content = content.decode() + except UnicodeDecodeError: + # for .png, .jpg, etc + pass + + return content + + def get_full_path(self): + """Returns file path from given file name""" + + file_path = self.file_url or self.file_name + + if "/" not in file_path: + file_path = "/files/" + file_path + + if file_path.startswith("/private/files/"): + file_path = get_files_path(*file_path.split("/private/files/", 1)[1].split("/"), is_private=1) + + elif file_path.startswith("/files/"): + file_path = get_files_path(*file_path.split("/files/", 1)[1].split("/")) + + else: + frappe.throw(_("There is some problem with the file url: {0}").format(file_path)) + + return file_path + + def write_file(self): + """write file to disk with a random name (to compare)""" + file_path = get_files_path(is_private=self.is_private) + + # create directory (if not exists) + frappe.create_folder(file_path) + # write the file + self.content = self.get_content() + if isinstance(self.content, text_type): + self.content = self.content.encode() + with open(os.path.join(file_path.encode('utf-8'), self.file_name.encode('utf-8')), 'wb+') as f: + f.write(self.content) + + return get_files_path(self.file_name, is_private=self.is_private) + + def get_file_doc(self): + '''returns File object (Document) from given parameters or form_dict''' + r = frappe.form_dict + + if self.file_url is None: self.file_url = r.file_url + if self.file_name is None: self.file_name = r.file_name + if self.attached_to_doctype is None: self.attached_to_doctype = r.doctype + if self.attached_to_name is None: self.attached_to_name = r.docname + if self.attached_to_field is None: self.attached_to_field = r.docfield + if self.folder is None: self.folder = r.folder + if self.is_private is None: self.is_private = r.is_private + + if r.filedata: + file_doc = self.save_uploaded() + + elif r.file_url: + file_doc = self.save() + + return file_doc + + + def save_uploaded(self): + self.content = self.get_uploaded_content() + if self.content: + return self.save() + else: + raise Exception + + + def validate_url(self, df=None): + if self.file_url: + if not self.file_url.startswith(("http://", "https://", "/files/", "/private/files/")): + frappe.throw("URL must start with 'http://' or 'https://'") + return + + self.file_url = unquote(self.file_url) + self.file_size = frappe.form_dict.file_size + + + def get_uploaded_content(self): + # should not be unicode when reading a file, hence using frappe.form + if 'filedata' in frappe.form_dict: + if "," in frappe.form_dict.filedata: + frappe.form_dict.filedata = frappe.form_dict.filedata.rsplit(",", 1)[1] + frappe.uploaded_content = base64.b64decode(frappe.form_dict.filedata) + return frappe.uploaded_content + elif self.content: + return self.content + frappe.msgprint(_('No file attached')) + return None + + + def save_file(self, content=None, decode=False): + file_exists = False + self.content = content + if decode: + if isinstance(content, text_type): + self.content = content.encode("utf-8") + + if b"," in self.content: + self.content = self.content.split(b",")[1] + self.content = base64.b64decode(self.content) + + if not self.is_private: + self.is_private = 0 + self.file_size = self.check_max_file_size() + self.content_hash = get_content_hash(self.content) + self.content_type = mimetypes.guess_type(self.file_name)[0] + + _file = frappe.get_value("File", {"content_hash": self.content_hash}, ["file_url"]) + if _file: + self.file_url = _file + file_exists = True + + if not file_exists: + if os.path.exists(encode(get_files_path(self.file_name))): + self.file_name = get_file_name(self.file_name, self.content_hash[-6:]) + + call_hook_method("before_write_file", file_size=self.file_size) + write_file_method = get_hook_method('write_file') + if write_file_method: + return write_file_method(self) + return self.save_file_on_filesystem() + + + def save_file_on_filesystem(self): + fpath = self.write_file() + + if self.is_private: + self.file_url = "/private/files/{0}".format(self.file_name) + else: + self.file_url = "/files/{0}".format(self.file_name) + + return { + 'file_name': os.path.basename(fpath), + 'file_url': self.file_url + } + + def get_file_data_from_hash(self): + for name in frappe.db.sql_list("select name from `tabFile` where content_hash=%s and is_private=%s", + (self.content_hash, self.is_private)): + b = frappe.get_doc('File', name) + return {k: b.get(k) for k in frappe.get_hooks()['write_file_keys']} + return False + + + def check_max_file_size(self): + max_file_size = get_max_file_size() + file_size = len(self.content) + + if file_size > max_file_size: + frappe.msgprint(_("File size exceeded the maximum allowed size of {0} MB").format( + max_file_size / 1048576), + raise_exception=MaxFileSizeReachedError) + + return file_size + + + def delete_file_data_content(self, only_thumbnail=False): + method = get_hook_method('delete_file_data_content') + if method: + method(self, only_thumbnail=only_thumbnail) + else: + self.delete_file_from_filesystem(only_thumbnail=only_thumbnail) + + + def delete_file_from_filesystem(self, only_thumbnail=False): + """Delete file, thumbnail from File document""" + if only_thumbnail: + delete_file(self.thumbnail_url) + else: + delete_file(self.file_url) + delete_file(self.thumbnail_url) + + + def check_file_permission(self): + for file in frappe.get_all("File", filters={"file_url": self.file_url, "is_private": 1}, + fields=["name", "attached_to_doctype", "attached_to_name"]): + + if (frappe.has_permission("File", ptype="read", doc=file.name) + or frappe.has_permission(file.attached_to_doctype, ptype="read", doc=file.attached_to_name)): + return True + + raise frappe.PermissionError + + + def get_extension(self): + '''returns split filename and extension''' + return os.path.splitext(self.file_name) + + def on_doctype_update(): frappe.db.add_index("File", ["attached_to_doctype", "attached_to_name"]) frappe.db.add_index("File", ["lft", "rgt"]) @@ -334,6 +600,7 @@ def create_new_folder(file_name, folder): @frappe.whitelist() def move_file(file_list, new_parent, old_parent): + if isinstance(file_list, string_types): file_list = json.loads(file_list) @@ -421,14 +688,171 @@ def get_web_image(file_url): return image, filename, extn -def check_file_permission(file_url): - for file in frappe.get_all("File", filters={"file_url": file_url, "is_private": 1}, fields=["name", "attached_to_doctype", "attached_to_name"]): - if (frappe.has_permission("File", ptype="read", doc=file.name) - or frappe.has_permission(file.attached_to_doctype, ptype="read", doc=file.attached_to_name)): - return True +def delete_file(path): + """Delete file from `public folder`""" + if path: + if ".." in path.split("/"): + frappe.msgprint(_("It is risky to delete this file: {0}. Please contact your System Manager.").format(path)) + + parts = os.path.split(path.strip("/")) + if parts[0]=="files": + path = frappe.utils.get_site_path("public", "files", parts[-1]) + + else: + path = frappe.utils.get_site_path("private", "files", parts[-1]) + + path = encode(path) + if os.path.exists(path): + os.remove(path) + + +def remove_file(fid=None, attached_to_doctype=None, attached_to_name=None, from_delete=False): + """Remove file and File entry""" + file_name = None + if not (attached_to_doctype and attached_to_name): + attached = frappe.db.get_value("File", fid, + ["attached_to_doctype", "attached_to_name", "file_name"]) + if attached: + attached_to_doctype, attached_to_name, file_name = attached + + ignore_permissions, comment = False, None + if attached_to_doctype and attached_to_name and not from_delete: + doc = frappe.get_doc(attached_to_doctype, attached_to_name) + ignore_permissions = doc.has_permission("write") or False + if frappe.flags.in_web_form: + ignore_permissions = True + if not file_name: + file_name = frappe.db.get_value("File", fid, "file_name") + comment = doc.add_comment("Attachment Removed", _("Removed {0}").format(file_name)) + frappe.delete_doc("File", fid, ignore_permissions=ignore_permissions) + + return comment + + +def get_max_file_size(): + return conf.get('max_file_size') or 10485760 + + +def remove_all(dt, dn, from_delete=False): + """remove all files in a transaction""" + try: + for fid in frappe.db.sql_list("""select name from `tabFile` where + attached_to_doctype=%s and attached_to_name=%s""", (dt, dn)): + remove_file(fid=fid, attached_to_doctype=dt, attached_to_name=dn, from_delete=from_delete) + except Exception as e: + if e.args[0]!=1054: raise # (temp till for patched) + + +def remove_file_by_url(file_url, doctype=None, name=None): + if doctype and name: + fid = frappe.db.get_value("File", { + "file_url": file_url, + "attached_to_doctype": doctype, + "attached_to_name": name}) + else: + fid = frappe.db.get_value("File", {"file_url": file_url}) + + if fid: + return remove_file(fid=fid) + + +def get_content_hash(content): + if isinstance(content, text_type): + content = content.encode() + return hashlib.md5(content).hexdigest() #nosec + + +def get_file_name(fname, optional_suffix): + # convert to unicode + fname = cstr(fname) + + f = fname.rsplit('.', 1) + if len(f) == 1: + partial, extn = f[0], "" + else: + partial, extn = f[0], "." + f[1] + return '{partial}{suffix}{extn}'.format(partial=partial, extn=extn, suffix=optional_suffix) + + +@frappe.whitelist() +def download_file(file_url): + """ + Download file using token and REST API. Valid session or + token is required to download private files. + + Method : GET + Endpoint : frappe.core.doctype.file.file.download_file + URL Params : file_name = /path/to/file relative to site path + """ + file_doc = frappe.get_doc("File", {"file_url": file_url}) + file_doc.check_permission("read") + path = os.path.join(get_files_path(), os.path.basename(file_url)) + + with open(path, "rb") as fileobj: + filedata = fileobj.read() + frappe.local.response.filename = os.path.basename(file_url) + frappe.local.response.filecontent = filedata + frappe.local.response.type = "download" + +def extract_images_from_doc(doc, fieldname): + content = doc.get(fieldname) + content = extract_images_from_html(doc, content) + if frappe.flags.has_dataurl: + doc.set(fieldname, content) + + +def extract_images_from_html(doc, content): + frappe.flags.has_dataurl = False + + def _save_file(match): + data = match.group(1) + data = data.split("data:")[1] + headers, content = data.split(",") + + if "filename=" in headers: + filename = headers.split("filename=")[-1] + + # decode filename + if not isinstance(filename, text_type): + filename = text_type(filename, 'utf-8') + else: + mtype = headers.split(";")[0] + filename = get_random_filename(content_type=mtype) + + doctype = doc.parenttype if doc.parent else doc.doctype + name = doc.parent or doc.name + + _file = frappe.get_doc({ + "doctype": "File", + "file_name": filename, + "attached_to_doctype": doctype, + "attached_to_name": name, + "content": content, + "decode": True}) + _file.save() + file_url = _file.file_url + if not frappe.flags.has_dataurl: + frappe.flags.has_dataurl = True + + return ']*src\s*=\s*["\'](?=data:)(.*?)["\']', _save_file, content) + + return content + + +def get_random_filename(extn=None, content_type=None): + if extn: + if not extn.startswith("."): + extn = "." + extn + + elif content_type: + extn = mimetypes.guess_extension(content_type) + + return random_string(7) + (extn or "") - raise frappe.PermissionError @frappe.whitelist() def unzip_file(name): @@ -436,6 +860,7 @@ def unzip_file(name): file_obj = frappe.get_doc('File', name) file_obj.unzip() + @frappe.whitelist() def get_attached_images(doctype, names): '''get list of image urls attached in form @@ -456,3 +881,11 @@ def get_attached_images(doctype, names): out[i.docname].append(i.file_url) return out + + +@frappe.whitelist() +def validate_filename(filename): + from frappe.utils import now_datetime + timestamp = now_datetime().strftime(" %Y-%m-%d %H:%M:%S") + fname = get_file_name(filename, timestamp) + return fname diff --git a/frappe/core/doctype/file/test_file.py b/frappe/core/doctype/file/test_file.py index 05dc677df6..dd1a7e7718 100644 --- a/frappe/core/doctype/file/test_file.py +++ b/frappe/core/doctype/file/test_file.py @@ -3,33 +3,191 @@ # See license.txt from __future__ import unicode_literals +import base64 import frappe +import os import unittest -from frappe.utils.file_manager import save_file, get_files_path from frappe import _ from frappe.core.doctype.file.file import move_file +from frappe.utils import get_files_path # test_records = frappe.get_test_records('File') +test_content1 = 'Hello' +test_content2 = 'Hello World' + + +def make_test_doc(): + d = frappe.new_doc('ToDo') + d.description = 'Test' + d.save() + return d.doctype, d.name + + +class TestSimpleFile(unittest.TestCase): + + + def setUp(self): + self.attached_to_doctype, self.attached_to_docname = make_test_doc() + self.test_content = test_content1 + _file = frappe.get_doc({ + "doctype": "File", + "file_name": "test1.txt", + "attached_to_doctype": self.attached_to_doctype, + "attached_to_name": self.attached_to_docname, + "content": self.test_content}) + _file.save() + self.saved_file_url = _file.file_url + + + def test_save(self): + _file = frappe.get_doc("File", {"file_url": self.saved_file_url}) + content = _file.get_content() + self.assertEqual(content, self.test_content) + + + def tearDown(self): + # File gets deleted on rollback, so blank + pass + + +class TestBase64File(unittest.TestCase): + + + def setUp(self): + self.attached_to_doctype, self.attached_to_docname = make_test_doc() + self.test_content = base64.b64encode(test_content1.encode('utf-8')) + _file = frappe.get_doc({ + "doctype": "File", + "file_name": "test_base64.txt", + "attached_to_doctype": self.attached_to_doctype, + "attached_to_docname": self.attached_to_docname, + "content": self.test_content, + "decode": True}) + _file.save() + self.saved_file_url = _file.file_url + + + def test_saved_content(self): + _file = frappe.get_doc("File", {"file_url": self.saved_file_url}) + content = _file.get_content() + self.assertEqual(content, test_content1) + + + def tearDown(self): + # File gets deleted on rollback, so blank + pass + + +class TestSameFileName(unittest.TestCase): + + + def setUp(self): + self.attached_to_doctype, self.attached_to_docname = make_test_doc() + self.test_content1 = test_content1 + self.test_content2 = test_content2 + _file1 = frappe.get_doc({ + "doctype": "File", + "file_name": "testing.txt", + "attached_to_doctype": self.attached_to_doctype, + "attached_to_name": self.attached_to_docname, + "content": self.test_content1}) + _file1.save() + _file2 = frappe.get_doc({ + "doctype": "File", + "file_name": "testing.txt", + "attached_to_doctype": self.attached_to_doctype, + "attached_to_name": self.attached_to_docname, + "content": self.test_content2}) + _file2.save() + self.saved_file_url1 = _file1.file_url + self.saved_file_url2 = _file2.file_url + + + def test_saved_content(self): + _file = frappe.get_doc("File", {"file_url": self.saved_file_url1}) + content1 = _file.get_content() + self.assertEqual(content1, self.test_content1) + _file = frappe.get_doc("File", {"file_url": self.saved_file_url2}) + content2 = _file.get_content() + self.assertEqual(content2, self.test_content2) + + + def tearDown(self): + # File gets deleted on rollback, so blank + pass + + +class TestSameContent(unittest.TestCase): + + + def setUp(self): + self.attached_to_doctype1, self.attached_to_docname1 = make_test_doc() + self.attached_to_doctype2, self.attached_to_docname2 = make_test_doc() + self.test_content1 = test_content1 + self.test_content2 = test_content1 + self.orig_filename = 'hello.txt' + self.dup_filename = 'hello2.txt' + _file1 = frappe.get_doc({ + "doctype": "File", + "file_name": self.orig_filename, + "attached_to_doctype": self.attached_to_doctype1, + "attached_to_name": self.attached_to_docname1, + "content": self.test_content1}) + _file1.save() + + _file2 = frappe.get_doc({ + "doctype": "File", + "file_name": self.dup_filename, + "attached_to_doctype": self.attached_to_doctype2, + "attached_to_name": self.attached_to_docname2, + "content": self.test_content2}) + + _file2.save() + + + def test_saved_content(self): + self.assertFalse(os.path.exists(get_files_path(self.dup_filename))) + + + def tearDown(self): + # File gets deleted on rollback, so blank + pass + + class TestFile(unittest.TestCase): + + def setUp(self): self.delete_test_data() self.upload_file() + def tearDown(self): try: frappe.get_doc("File", {"file_name": "file_copy.txt"}).delete() except frappe.DoesNotExistError: pass + def delete_test_data(self): for f in frappe.db.sql('''select name, file_name from tabFile where is_home_folder = 0 and is_attachments_folder = 0 order by rgt-lft asc'''): frappe.delete_doc("File", f[0]) + def upload_file(self): - self.saved_file = save_file('file_copy.txt', "Testing file copy example.",\ - "", "", self.get_folder("Test Folder 1", "Home").name) - self.saved_filename = get_files_path(self.saved_file.file_name) + _file = frappe.get_doc({ + "doctype": "File", + "file_name": "file_copy.txt", + "attached_to_name": "", + "attached_to_doctype": "", + "folder": self.get_folder("Test Folder 1", "Home").name, + "content": "Testing file copy example."}) + _file.save() + self.saved_folder = _file.folder + self.saved_name = _file.name + self.saved_filename = get_files_path(_file.file_name) + def get_folder(self, folder_name, parent_folder="Home"): return frappe.get_doc({ @@ -39,30 +197,39 @@ class TestFile(unittest.TestCase): "folder": _(parent_folder) }).insert() + def tests_after_upload(self): - self.assertEqual(self.saved_file.folder, _("Home/Test Folder 1")) + self.assertEqual(self.saved_folder, _("Home/Test Folder 1")) folder_size = frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size") - saved_file_size = frappe.db.get_value("File", self.saved_file.name, "file_size") + saved_file_size = frappe.db.get_value("File", self.saved_name, "file_size") self.assertEqual(folder_size, saved_file_size) + def test_file_copy(self): folder = self.get_folder("Test Folder 2", "Home") - file = frappe.get_doc("File", {"file_name":"file_copy.txt"}) + file = frappe.get_doc("File", {"file_name": "file_copy.txt"}) move_file([{"name": file.name}], folder.name, file.folder) - file = frappe.get_doc("File", {"file_name":"file_copy.txt"}) + file = frappe.get_doc("File", {"file_name": "file_copy.txt"}) self.assertEqual(_("Home/Test Folder 2"), file.folder) self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 2"), "file_size"), file.file_size) self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size"), 0) + def test_folder_copy(self): folder = self.get_folder("Test Folder 2", "Home") folder = self.get_folder("Test Folder 3", "Home/Test Folder 2") - - self.saved_file = save_file('folder_copy.txt', "Testing folder copy example.", "", "", folder.name) + _file = frappe.get_doc({ + "doctype": "File", + "file_name": "folder_copy.txt", + "attached_to_name": "", + "attached_to_doctype": "", + "folder": folder.name, + "content": "Testing folder copy example"}) + _file.save() move_file([{"name": folder.name}], 'Home/Test Folder 1', folder.folder) @@ -75,29 +242,39 @@ class TestFile(unittest.TestCase): self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size"), file.file_size) self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 2"), "file_size"), 0) - def test_non_parent_folder(self): + + def test_default_folder(self): d = frappe.get_doc({ "doctype": "File", "file_name": _("Test_Folder"), "is_folder": 1 }) + d.save() + self.assertEqual(d.folder, "Home") - self.assertRaises(frappe.ValidationError, d.save) def test_on_delete(self): - file = frappe.get_doc("File", {"file_name":"file_copy.txt"}) + file = frappe.get_doc("File", {"file_name": "file_copy.txt"}) file.delete() self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size"), 0) folder = self.get_folder("Test Folder 3", "Home/Test Folder 1") - self.saved_file = save_file('folder_copy.txt', "Testing folder copy example.", "", "", folder.name) + _file = frappe.get_doc({ + "doctype": "File", + "file_name": "folder_copy.txt", + "attached_to_name": "", + "attached_to_doctype": "", + "folder": folder.name, + "content": "Testing folder copy example"}) + _file.save() folder = frappe.get_doc("File", "Home/Test Folder 1/Test Folder 3") self.assertRaises(frappe.ValidationError, folder.delete) + def test_file_upload_limit(self): - from frappe.utils.file_manager import MaxFileSizeReachedError + from frappe.core.doctype.file.file import MaxFileSizeReachedError from frappe.limits import update_limits, clear_limit from frappe import _dict @@ -114,8 +291,15 @@ class TestFile(unittest.TestCase): # Rebuild the frappe.local.conf to take up the changes from site_config frappe.local.conf = _dict(frappe.get_site_config()) - self.assertRaises(MaxFileSizeReachedError, save_file, '_test_max_space.txt', - 'This files test for max space usage', "", "", self.get_folder("Test Folder 2", "Home").name) + _file = frappe.get_doc({ + "doctype": "File", + "file_name": "_test_max_space.txt", + "attached_to_name": "", + "attached_to_doctype": "", + "folder": self.get_folder("Test Folder 2", "Home").name, + "content": "This file tests for max space usage"}) + self.assertRaises(MaxFileSizeReachedError, + _file.save) # Scrub the site_config and rebuild frappe.local.conf clear_limit("space") diff --git a/frappe/core/doctype/prepared_report/prepared_report.py b/frappe/core/doctype/prepared_report/prepared_report.py index 3d8481c696..78afb3c183 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.py +++ b/frappe/core/doctype/prepared_report/prepared_report.py @@ -11,10 +11,10 @@ import frappe from frappe.model.document import Document from frappe.utils.background_jobs import enqueue from frappe.desk.query_report import generate_report_result, get_columns_dict -from frappe.utils.file_manager import save_file, remove_all +from frappe.core.doctype.file.file import remove_all from frappe.utils.csvutils import to_csv, read_csv_content_from_attached_file from frappe.desk.form.load import get_attachments -from frappe.utils.file_manager import download_file +from frappe.core.doctype.file.file import download_file class PreparedReport(Document): @@ -61,15 +61,15 @@ def create_csv_file(columns, data, dt, dn): csv_filename = '{0}.csv'.format(frappe.utils.data.format_datetime(frappe.utils.now(), "Y-m-d-H:M")) rows = [tuple(columns)] + data encoded = base64.b64encode(frappe.safe_encode(to_csv(rows))) - # Call save_file function to upload and attach the file - save_file( - fname=csv_filename, - content=encoded, - dt=dt, - dn=dn, - folder=None, - decode=True, - is_private=False) + # Call save() file function to upload and attach the file + _file = frappe.get_doc({ + "doctype": "File", + "file_name": csv_filename, + "attached_to_doctype": dt, + "attached_to_name": dn, + "content": encoded, + "decode": True}) + _file.save() @frappe.whitelist() diff --git a/frappe/desk/form/utils.py b/frappe/desk/form/utils.py index 053102d368..cee34394f8 100644 --- a/frappe/desk/form/utils.py +++ b/frappe/desk/form/utils.py @@ -13,9 +13,11 @@ from six import string_types @frappe.whitelist() def remove_attach(): """remove attachment""" - import frappe.utils.file_manager fid = frappe.form_dict.get('fid') - return frappe.utils.file_manager.remove_file(fid) + file_name = frappe.form_dict.get('file_name') + frappe.delete_doc('File', fid) + return doc.add_comment("Attachment Removed", _("Removed {0}").format(file_name)) + @frappe.whitelist() def validate_link(): diff --git a/frappe/desk/page/setup_wizard/setup_wizard.py b/frappe/desk/page/setup_wizard/setup_wizard.py index eef203ab04..82f44ee249 100755 --- a/frappe/desk/page/setup_wizard/setup_wizard.py +++ b/frappe/desk/page/setup_wizard/setup_wizard.py @@ -7,7 +7,6 @@ import frappe, json, os from frappe.utils import strip, cint from frappe.translate import (set_default_language, get_dict, send_translations) from frappe.geo.country_info import get_country_info -from frappe.utils.file_manager import save_file from frappe.utils.password import update_password from werkzeug.useragents import UserAgent from . import install_fixtures @@ -187,7 +186,15 @@ def update_user_name(args): attach_user = args.get("attach_user").split(",") if len(attach_user)==3: filename, filetype, content = attach_user - fileurl = save_file(filename, content, "User", args.get("name"), decode=True).file_url + _file = frappe.get_doc({ + "doctype": "File", + "file_name": filename, + "attached_to_doctype": "User", + "attached_to_name": args.get("name"), + "content": content, + "decode": True}) + _file.save() + fileurl = _file.file_url frappe.db.set_value("User", args.get("name"), "user_image", fileurl) if args.get('name'): diff --git a/frappe/email/doctype/email_account/test_email_account.py b/frappe/email/doctype/email_account/test_email_account.py index 6fa7ff30ed..849cb6b42c 100644 --- a/frappe/email/doctype/email_account/test_email_account.py +++ b/frappe/email/doctype/email_account/test_email_account.py @@ -8,7 +8,6 @@ test_records = frappe.get_test_records('Email Account') from frappe.core.doctype.communication.email import make from frappe.desk.form.load import get_attachments -from frappe.utils.file_manager import delete_file_from_filesystem from frappe.email.doctype.email_account.email_account import notify_unreplied from datetime import datetime, timedelta @@ -55,7 +54,6 @@ class TestEmailAccount(unittest.TestCase): frappe.db.sql("DELETE FROM `tabCommunication` WHERE sender='test_sender@example.com'") existing_file = frappe.get_doc({'doctype': 'File', 'file_name': 'erpnext-conf-14.png'}) frappe.delete_doc("File", existing_file.name) - delete_file_from_filesystem(existing_file) with open(os.path.join(os.path.dirname(__file__), "test_mails", "incoming-2.raw"), "r") as testfile: test_mails = [testfile.read()] @@ -73,7 +71,7 @@ class TestEmailAccount(unittest.TestCase): # cleanup existing_file = frappe.get_doc({'doctype': 'File', 'file_name': 'erpnext-conf-14.png'}) frappe.delete_doc("File", existing_file.name) - delete_file_from_filesystem(existing_file) + def test_incoming_attached_email_from_outlook_plain_text_only(self): frappe.db.sql("delete from tabCommunication where sender='test_sender@example.com'") diff --git a/frappe/email/email_body.py b/frappe/email/email_body.py index 8ab7ae5c85..7cb7be1945 100755 --- a/frappe/email/email_body.py +++ b/frappe/email/email_body.py @@ -144,12 +144,12 @@ class EMail: def attach_file(self, n): """attach a file from the `FileData` table""" - from frappe.utils.file_manager import get_file - res = get_file(n) - if not res: + _file = frappe.get_doc("File", {"file_name": n}) + content = _file.get_content() + if not content: return - self.add_attachment(res[0], res[1]) + self.add_attachment(_file.file_name, content) def add_attachment(self, fname, fcontent, content_type=None, parent=None, content_id=None, inline=False): diff --git a/frappe/email/queue.py b/frappe/email/queue.py index d299658878..621a81e2cb 100755 --- a/frappe/email/queue.py +++ b/frappe/email/queue.py @@ -11,7 +11,6 @@ from frappe.email.email_body import get_email, get_formatted_html, add_attachmen from frappe.utils.verified_command import get_signed_params, verify_request from html2text import html2text from frappe.utils import get_url, nowdate, encode, now_datetime, add_days, split_emails, cstr, cint -from frappe.utils.file_manager import get_file from rq.timeouts import JobTimeoutException from frappe.utils.scheduler import log from six import text_type, string_types @@ -531,9 +530,10 @@ def prepare_message(email, recipient, recipients_list): fid = attachment.get("fid") if fid: - fname, fcontent = get_file(fid) + _file = frappe.get_doc("File", {"file_name": fid}) + fcontent = _file.get_content() attachment.update({ - 'fname': fname, + 'fname': _file.file_name, 'fcontent': fcontent, 'parent': msg_obj }) diff --git a/frappe/email/receive.py b/frappe/email/receive.py index f77e8a80d9..ffee8dc17a 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -13,7 +13,7 @@ from frappe import _, safe_decode, safe_encode from frappe.utils import (extract_email_id, convert_utc_to_user_timezone, now, cint, cstr, strip, markdown, parse_addr) from frappe.utils.scheduler import log -from frappe.utils.file_manager import get_random_filename, save_file, MaxFileSizeReachedError +from frappe.core.doctype.file.file import get_random_filename, MaxFileSizeReachedError class EmailSizeExceededError(frappe.ValidationError): pass class EmailTimeoutError(frappe.ValidationError): pass @@ -523,12 +523,18 @@ class Email: for attachment in self.attachments: try: - file_data = save_file(attachment['fname'], attachment['fcontent'], - doc.doctype, doc.name, is_private=1) - saved_attachments.append(file_data) + _file = frappe.get_doc({ + "doctype": "File", + "file_name": attachment['fname'], + "attached_to_doctype": doc.doctype, + "attached_to_name": doc.name, + "is_private": 1, + "content": attachment['fcontent']}) + _file.save() + saved_attachments.append(_file) if attachment['fname'] in self.cid_map: - self.cid_map[file_data.name] = self.cid_map[attachment['fname']] + self.cid_map[_file.name] = self.cid_map[attachment['fname']] except MaxFileSizeReachedError: # WARNING: bypass max file size exception diff --git a/frappe/handler.py b/frappe/handler.py index 16138edf62..7a040871ad 100755 --- a/frappe/handler.py +++ b/frappe/handler.py @@ -6,7 +6,6 @@ import frappe from frappe import _ import frappe.utils import frappe.sessions -import frappe.utils.file_manager import frappe.desk.form.run_method from frappe.utils.response import build_response from werkzeug.wrappers import Response @@ -111,7 +110,18 @@ def uploadfile(): try: if frappe.form_dict.get('from_form'): try: - ret = frappe.utils.file_manager.upload() + ret = frappe.get_doc({ + "doctype": "File", + "attached_to_name": frappe.form_dict.docname, + "attached_to_doctype": frappe.form_dict.doctype, + "attached_to_field": frappe.form_dict.docfield, + "file_url": frappe.form_dict.file_url, + "file_name": frappe.form_dict.filename, + "is_private": frappe.utils.cint(frappe.form_dict.is_private), + "content": frappe.form_dict.filedata, + "decode": True + }) + ret.save() except frappe.DuplicateEntryError: # ignore pass ret = None diff --git a/frappe/integrations/doctype/gsuite_templates/gsuite_templates.py b/frappe/integrations/doctype/gsuite_templates/gsuite_templates.py index ff85ac8398..83f379ee29 100644 --- a/frappe/integrations/doctype/gsuite_templates/gsuite_templates.py +++ b/frappe/integrations/doctype/gsuite_templates/gsuite_templates.py @@ -7,7 +7,6 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.integrations.doctype.gsuite_settings.gsuite_settings import run_gsuite_script -from frappe.utils.file_manager import save_url class GSuiteTemplates(Document): pass @@ -25,7 +24,16 @@ def create_gsuite_doc(doctype, docname, gs_template=None): r = run_gsuite_script('new', filename, templ.template_id, templ.destination_id, json_data) - filedata = save_url(r['url'], filename, doctype, docname, "Home/Attachments", True) + _file = frappe.get_doc({ + "doctype": "File", + "file_url": r['url'], + "file_name": filename, + "attached_to_doctype": doctype, + "attached_to_name": docname, + "attached_to_field": True, + "folder": "Home/Attachments"}) + _file.save() + comment = frappe.get_doc(doctype, docname).add_comment("Attachment", _("added {0}").format("{file_name}{icon}".format(**{ "icon": ' ' if filedata.is_private else "", diff --git a/frappe/limits.py b/frappe/limits.py index 93e0e05ee0..fe800e3fe5 100755 --- a/frappe/limits.py +++ b/frappe/limits.py @@ -173,7 +173,7 @@ def clear_limit(key): def validate_space_limit(file_size): """Stop from writing file if max space limit is reached""" - from frappe.utils.file_manager import MaxFileSizeReachedError + from frappe.core.doctype.file.file import MaxFileSizeReachedError limits = get_limits() if not limits.space: diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index aa6e309915..449bfa0262 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -774,7 +774,7 @@ class BaseDocument(object): return cast_fieldtype(df.fieldtype, value) def _extract_images_from_text_editor(self): - from frappe.utils.file_manager import extract_images_from_doc + from frappe.core.doctype.file.file import extract_images_from_doc if self.doctype != "DocType": for df in self.meta.get("fields", {"fieldtype": ('=', "Text Editor")}): extract_images_from_doc(self, df.fieldname) diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py index 34c5da61ab..2b8a747643 100644 --- a/frappe/model/delete_doc.py +++ b/frappe/model/delete_doc.py @@ -7,7 +7,7 @@ import frappe import frappe.model.meta from frappe.model.dynamic_links import get_dynamic_link_map import frappe.defaults -from frappe.utils.file_manager import remove_all +from frappe.core.doctype.file.file import remove_all from frappe.utils.password import delete_all_passwords_for from frappe import _ from frappe.model.naming import revert_series_if_last diff --git a/frappe/model/document.py b/frappe/model/document.py index 505a687f4e..e30f2fc0a1 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -14,7 +14,6 @@ from werkzeug.exceptions import NotFound, Forbidden import hashlib, json from frappe.model import optional_fields from frappe.model.workflow import validate_workflow -from frappe.utils.file_manager import save_url from frappe.utils.global_search import update_global_search from frappe.integrations.doctype.webhook import run_webhooks @@ -322,7 +321,15 @@ class Document(BaseDocument): for attach_item in get_attachments(self.doctype, self.amended_from): #save attachments to new doc - save_url(attach_item.file_url, attach_item.file_name, self.doctype, self.name, "Home/Attachments", attach_item.is_private) + _file = frappe.get_doc({ + "doctype": "File", + "file_url": attach_item.file_url, + "file_name": attach_item.file_name, + "attached_to_name": self.name, + "attached_to_doctype": self.doctype, + "folder": "Home/Attachments"}) + _file.save() + def update_children(self): '''update child tables''' diff --git a/frappe/patches/v4_0/file_manager_hooks.py b/frappe/patches/v4_0/file_manager_hooks.py index 60686b1e0e..6be3b25124 100644 --- a/frappe/patches/v4_0/file_manager_hooks.py +++ b/frappe/patches/v4_0/file_manager_hooks.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals, print_function import frappe import os from frappe.utils import get_files_path -from frappe.utils.file_manager import get_content_hash, get_file +from frappe.core.doctype.file.file import get_content_hash def execute(): @@ -22,7 +22,8 @@ def execute(): else: b.file_url = os.path.normpath('/files/' + old_file_name) try: - _file_name, content = get_file(name) + _file = frappe.get_doc("File", {"file_name": name}) + content = _file.get_content() b.content_hash = get_content_hash(content) except IOError: print('Warning: Error processing ', name) diff --git a/frappe/patches/v4_1/file_manager_fix.py b/frappe/patches/v4_1/file_manager_fix.py index 1fced61799..cd30c94177 100644 --- a/frappe/patches/v4_1/file_manager_fix.py +++ b/frappe/patches/v4_1/file_manager_fix.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals, print_function import frappe import os -from frappe.utils.file_manager import get_content_hash, get_file, get_file_name +from frappe.core.doctype.file.file import get_content_hash, get_file_name from frappe.utils import get_files_path, get_site_path # The files missed by the previous patch might have been replaced with new files @@ -36,7 +36,8 @@ def execute(): else: b.file_url = os.path.normpath('/files/' + old_file_name) try: - _file_name, content = get_file(name) + _file = frappe.get_doc("File", {"file_name": name}) + content = _file.get_content() b.content_hash = get_content_hash(content) except IOError: print('Warning: Error processing ', name) diff --git a/frappe/printing/doctype/print_format/print_format.py b/frappe/printing/doctype/print_format/print_format.py index ea66665572..8f25ab7cb8 100644 --- a/frappe/printing/doctype/print_format/print_format.py +++ b/frappe/printing/doctype/print_format/print_format.py @@ -31,7 +31,7 @@ class PrintFormat(Document): validate_template(self.html) def extract_images(self): - from frappe.utils.file_manager import extract_images_from_html + from frappe.core.doctype.file.file import extract_images_from_html if self.format_data: data = json.loads(self.format_data) for df in data: diff --git a/frappe/public/js/frappe/upload.js b/frappe/public/js/frappe/upload.js index bdc8b41316..ca2ed264ed 100644 --- a/frappe/public/js/frappe/upload.js +++ b/frappe/public/js/frappe/upload.js @@ -317,7 +317,7 @@ frappe.upload = { } else { args.file_size = fileobj.size; frappe.call({ - method: 'frappe.utils.file_manager.validate_filename', + method: 'frappe.core.doctype.file.file.validate_filename', args: {"filename": args.filename}, callback: function(r) { args.filename = r.message; diff --git a/frappe/twofactor.py b/frappe/twofactor.py index 03d23744dd..33bade873c 100644 --- a/frappe/twofactor.py +++ b/frappe/twofactor.py @@ -337,13 +337,19 @@ def get_qr_svg_code(totp_uri): def qrcode_as_png(user, totp_uri): '''Save temporary Qrcode to server.''' - from frappe.utils.file_manager import save_file folder = create_barcode_folder() png_file_name = '{}.png'.format(frappe.generate_hash(length=20)) - file_obj = save_file(png_file_name, png_file_name, 'User', user, folder=folder) + _file = frappe.get_doc({ + "doctype": "File", + "file_name": png_file_name, + "attached_to_doctype": 'User', + "attached_to_name": user, + "folder": folder, + "content": png_file_name}) + _file.save() frappe.db.commit() - file_url = get_url(file_obj.file_url) - file_path = os.path.join(frappe.get_site_path('public', 'files'), file_obj.file_name) + file_url = get_url(_file.file_url) + file_path = os.path.join(frappe.get_site_path('public', 'files'), _file.file_name) url = qrcreate(totp_uri) with open(file_path, 'w') as png_file: url.png(png_file, scale=8, module_color=[0, 0, 0, 180], background=[0xff, 0xff, 0xcc]) diff --git a/frappe/utils/csvutils.py b/frappe/utils/csvutils.py index 7476eac0c3..72b03ae9d0 100644 --- a/frappe/utils/csvutils.py +++ b/frappe/utils/csvutils.py @@ -15,8 +15,8 @@ def read_csv_content_from_uploaded_file(ignore_encoding=False): with open(frappe.uploaded_file, "r") as upfile: fcontent = upfile.read() else: - from frappe.utils.file_manager import get_uploaded_content - fname, fcontent = get_uploaded_content() + _file = frappe.new_doc("File") + fcontent = _file.get_uploaded_content() return read_csv_content(fcontent, ignore_encoding) def read_csv_content_from_attached_file(doc): @@ -30,8 +30,8 @@ def read_csv_content_from_attached_file(doc): raise Exception try: - from frappe.utils.file_manager import get_file - fname, fcontent = get_file(fileid) + _file = frappe.get_doc("File", {"file_name": fileid}) + fcontent = _file.get_content() return read_csv_content(fcontent, frappe.form_dict.get('ignore_encoding_errors')) except Exception: frappe.throw(_("Unable to open attached file. Did you export it as CSV?"), title=_('Invalid CSV Format')) diff --git a/frappe/utils/response.py b/frappe/utils/response.py index 78eb88cee1..284d1fbc25 100644 --- a/frappe/utils/response.py +++ b/frappe/utils/response.py @@ -17,7 +17,6 @@ from werkzeug.local import LocalProxy from werkzeug.wsgi import wrap_file from werkzeug.wrappers import Response from werkzeug.exceptions import NotFound, Forbidden -from frappe.core.doctype.file.file import check_file_permission from frappe.website.render import render from frappe.utils import cint from six import text_type @@ -147,7 +146,8 @@ def download_backup(path): def download_private_file(path): """Checks permissions and sends back private file""" try: - check_file_permission(path) + _file = frappe.get_doc("File", {"file_url": path}) + _file.check_file_permission() except frappe.PermissionError: raise Forbidden(_("You don't have permission to access this file")) diff --git a/frappe/utils/xlsxutils.py b/frappe/utils/xlsxutils.py index 880726c16e..4354f56d21 100644 --- a/frappe/utils/xlsxutils.py +++ b/frappe/utils/xlsxutils.py @@ -70,8 +70,8 @@ def handle_html(data): def read_xlsx_file_from_attached_file(file_id=None, fcontent=None, filepath=None): if file_id: - from frappe.utils.file_manager import get_file_path - filename = get_file_path(file_id) + _file = frappe.get_doc("File", {"file_name": file_id}) + filename = _file.get_full_path() elif fcontent: from io import BytesIO filename = BytesIO(fcontent) diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py index b1d5708444..d2ffed7046 100644 --- a/frappe/website/doctype/web_form/web_form.py +++ b/frappe/website/doctype/web_form/web_form.py @@ -6,10 +6,10 @@ import frappe, json, os from frappe.website.website_generator import WebsiteGenerator from frappe import _, scrub from frappe.utils import cstr -from frappe.utils.file_manager import save_file, remove_file_by_url from frappe.website.utils import get_comment_list from frappe.custom.doctype.customize_form.customize_form import docfield_properties -from frappe.utils.file_manager import get_max_file_size +from frappe.core.doctype.file.file import get_max_file_size +from frappe.core.doctype.file.file import remove_file_by_url from frappe.modules.utils import export_module_json, get_doc_module from six.moves.urllib.parse import urlencode from frappe.integrations.utils import get_payment_gateway_controller @@ -416,22 +416,29 @@ def accept(web_form, data, for_payment=False): # remove earlier attached file (if exists) if doc.get(fieldname): - remove_file_by_url(doc.get(fieldname), doc.doctype, doc.name) + remove_file_by_url(doc.get(fieldname), doctype=doc.doctype, name=doc.name) # save new file filename, dataurl = filedata.split(',', 1) - filedoc = save_file(filename, dataurl, - doc.doctype, doc.name, decode=True) + _file = frappe.get_doc({ + "doctype": "File", + "file_name": filename, + "attached_to_doctype": doc.doctype, + "attached_to_name": doc.name, + "content": dataurl, + "decode": True}) + _file.save() # update values - doc.set(fieldname, filedoc.file_url) + doc.set(fieldname, _file.file_url) doc.save(ignore_permissions = True) if files_to_delete: for f in files_to_delete: if f: - remove_file_by_url(f, doc.doctype, doc.name) + remove_file_by_url(doc.get(fieldname), doctype=doc.doctype, name=doc.name) + frappe.flags.web_form_doc = doc