file-api: migration improvements and fixes

* migrate more functions to file class
* add get_content(), returns file content from file_name
* move get_file_path() to get_full_path() to decrease naming ambiguity

Signed-off-by: Chinmay Pai <chinmaydpai@gmail.com>
This commit is contained in:
Chinmay Pai 2018-09-06 17:30:33 +05:30
parent a80c23c9be
commit 8943f6cfd5
No known key found for this signature in database
GPG key ID: 75507BE256F40CED
12 changed files with 141 additions and 136 deletions

View file

@ -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.core.doctype.file.file 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
@ -285,7 +284,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})
content = _file.get_content()
# these attachments will be attached on-demand
# and won't be stored in the message
doc.attachments.append({"fid": a})

View file

@ -272,9 +272,9 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False,
# header
filename, file_extension = ['','']
if not rows:
from frappe.core.doctype.file.file 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_name": data_import_doc.import_file})
fcontent = _file.get_content()
filename, file_extension = os.path.splitext(_file.file_name)
if file_extension == '.xlsx' and from_data_import == 'Yes':
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file

View file

@ -345,6 +345,61 @@ class File(NestedSet):
"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
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'''
@ -368,7 +423,7 @@ class File(NestedSet):
def save_uploaded(self):
self.file_name, self.content = self.get_uploaded_content()
self.content = self.get_uploaded_content()
if self.content:
return self.save_file(content=self.content)
else:
@ -408,12 +463,11 @@ class File(NestedSet):
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)
frappe.uploaded_filename = frappe.form_dict.filename
return frappe.uploaded_filename, frappe.uploaded_content
elif self.content and self.file_name:
return self.file_name, self.content
return frappe.uploaded_content
elif self.content:
return self.content
frappe.msgprint(_('No file attached'))
return None, None
return None
def save_file(self, content=None, decode=False):
@ -436,8 +490,11 @@ class File(NestedSet):
if not file_data:
call_hook_method("before_write_file", file_size=self.file_size)
write_file_method = get_hook_method('write_file', fallback=save_file_on_filesystem)
file_data = write_file_method(self.file_name, self.content, self.content_type, self.is_private)
write_file_method = get_hook_method('write_file')
if write_file_method:
file_data = write_file_method(self)
else:
file_data = self.save_file_on_filesystem()
file_data = copy(file_data)
file_data.update({
@ -460,6 +517,18 @@ class File(NestedSet):
return f
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",
@ -482,8 +551,31 @@ class File(NestedSet):
def delete_file_data_content(self, only_thumbnail=False):
method = get_hook_method('delete_file_data_content', fallback=delete_file_from_filesystem)
method(self, only_thumbnail=only_thumbnail)
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 on_doctype_update():
@ -615,44 +707,6 @@ def get_web_image(file_url):
return image, filename, extn
def save_file_on_filesystem(file_name, content, content_type, is_private):
fpath = write_file(file_name, content, is_private=is_private)
if is_private:
file_url = "/private/files/{0}".format(file_name)
else:
file_url = "/files/{0}".format(file_name)
return {
'file_name': os.path.basename(fpath),
'file_url': file_url
}
def write_file(file_name, content, is_private=0):
"""write file to disk with a random name (to compare)"""
file_path = get_files_path(is_private=is_private)
# create directory (if not exists)
frappe.create_folder(file_path)
# write the file
if isinstance(content, text_type):
content = content.encode()
with open(os.path.join(file_path.encode('utf-8'), file_name.encode('utf-8')), 'wb+') as f:
f.write(content)
return get_files_path(file_name, is_private=is_private)
def delete_file_from_filesystem(doc, only_thumbnail=False):
"""Delete file, thumbnail from File document"""
if only_thumbnail:
delete_file(doc.thumbnail_url)
else:
delete_file(doc.file_url)
delete_file(doc.thumbnail_url)
def delete_file(path):
"""Delete file from `public folder`"""
if path:
@ -719,51 +773,6 @@ def remove_file_by_url(file_url, doctype=None, name=None):
return remove_file(fid=fid)
def get_file(fname):
"""Returns [`file_name`, `content`] for given file name `fname`"""
file_path = get_file_path(fname)
# 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 [file_path.rsplit("/", 1)[-1], content]
def get_file_path(file_name):
"""Returns file path from given file name"""
f = frappe.db.sql("""select file_url from `tabFile`
where name=%s or file_name=%s""", (file_name, file_name))
if f:
file_name = f[0][0]
file_path = 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 get_content_hash(content):
if isinstance(content, text_type):
content = content.encode()
@ -795,7 +804,7 @@ def download_file(file_url):
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 = 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))
@ -859,16 +868,6 @@ def get_random_filename(extn=None, content_type=None):
return random_string(7) + (extn or "")
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
raise frappe.PermissionError
@frappe.whitelist()
def unzip_file(name):
'''Unzip the given file and make file records for each of the extracted files'''

View file

@ -7,7 +7,7 @@ import frappe
import os
import unittest
from frappe import _
from frappe.core.doctype.file.file import move_file, get_file, get_files_path
from frappe.core.doctype.file.file import move_file, get_files_path
# test_records = frappe.get_test_records('File')
test_content1 = 'Hello'
@ -33,7 +33,8 @@ class TestSimpleFile(unittest.TestCase):
self.saved_filename = get_files_path(self.saved_file.file_name)
def test_save(self):
_, content = get_file(self.saved_file.name)
_file = frappe.get_doc("File", {"file_name": self.saved_file.file_name})
content = _file.get_content()
self.assertEqual(content, self.test_content)
def tearDown(self):
@ -61,9 +62,11 @@ class TestSameFileName(unittest.TestCase):
self.saved_filename2 = get_files_path(self.saved_file2.file_name)
def test_saved_content(self):
_, content1 = get_file(self.saved_file1.name)
_file = frappe.get_doc("File", {"file_name": self.saved_file1.file_name})
content1 = _file.get_content()
self.assertEqual(content1, self.test_content1)
_, content2 = get_file(self.saved_file2.name)
_file = frappe.get_doc("File", {"file_name": self.saved_file2.file_name})
content2 = _file.get_content()
self.assertEqual(content2, self.test_content2)
def tearDown(self):
@ -94,8 +97,10 @@ class TestSameContent(unittest.TestCase):
self.saved_filename2 = get_files_path(self.saved_file2.file_name)
def test_saved_content(self):
filename1, content1 = get_file(self.saved_file1.name)
filename2, content2 = get_file(self.saved_file2.name)
_file1 = frappe.get_doc("File", {"file_name": self.saved_file1.file_name})
filename1 = _file1.file_name
_file2 = frappe.get_doc("File", {"file_name": self.saved_file2.file_name})
filename2 = _file2.file_name
self.assertEqual(filename1, filename2)
self.assertFalse(os.path.exists(get_files_path(self.dup_filename)))

View file

@ -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.core.doctype.file.file import delete_file_from_filesystem
from frappe.email.doctype.email_account.email_account import notify_unreplied
from datetime import datetime, timedelta
@ -55,7 +54,7 @@ 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)
existing_file.delete_file_from_filesystem()
with open(os.path.join(os.path.dirname(__file__), "test_mails", "incoming-2.raw"), "r") as testfile:
test_mails = [testfile.read()]
@ -73,7 +72,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)
existing_file.delete_file_from_filesystem()
def test_incoming_attached_email_from_outlook_plain_text_only(self):

View file

@ -144,12 +144,12 @@ class EMail:
def attach_file(self, n):
"""attach a file from the `FileData` table"""
from frappe.core.doctype.file.file 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):

View file

@ -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.core.doctype.file.file 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
})

View file

@ -6,7 +6,7 @@ from __future__ import unicode_literals, print_function
import frappe
import os
from frappe.utils import get_files_path
from frappe.core.doctype.file.file 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)

View file

@ -5,7 +5,7 @@ from __future__ import unicode_literals, print_function
import frappe
import os
from frappe.core.doctype.file.file 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)

View file

@ -16,7 +16,7 @@ def read_csv_content_from_uploaded_file(ignore_encoding=False):
fcontent = upfile.read()
else:
_file = frappe.new_doc("File")
_, fcontent = _file.get_uploaded_content()
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.core.doctype.file.file 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'))

View file

@ -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"))

View file

@ -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.core.doctype.file.file 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)