Saving to disk: Fix errors on Linux/macOS if the title/authors are long enough to make individual path components larger than 255 characters. Fixes #1807525 [[Enhancement] Filename length during saving on disk](https://bugs.launchpad.net/calibre/+bug/1807525)

This commit is contained in:
Kovid Goyal 2018-12-29 16:20:14 +05:30
parent 19b02bd063
commit 2e33cb5cb3
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 20 additions and 15 deletions

View File

@ -12,7 +12,7 @@ from calibre.constants import DEBUG
from calibre.db.errors import NoSuchFormat from calibre.db.errors import NoSuchFormat
from calibre.utils.config import Config, StringConfig, tweaks from calibre.utils.config import Config, StringConfig, tweaks
from calibre.utils.formatter import TemplateFormatter from calibre.utils.formatter import TemplateFormatter
from calibre.utils.filenames import shorten_components_to, supports_long_names, ascii_filename from calibre.utils.filenames import shorten_components_to, ascii_filename
from calibre.constants import preferred_encoding from calibre.constants import preferred_encoding
from calibre.ebooks.metadata import fmt_sidx from calibre.ebooks.metadata import fmt_sidx
from calibre.ebooks.metadata import title_sort from calibre.ebooks.metadata import title_sort
@ -378,7 +378,7 @@ def sanitize_args(root, opts):
root = os.path.abspath(root) root = os.path.abspath(root)
opts.template = preprocess_template(opts.template) opts.template = preprocess_template(opts.template)
length = 1000 if supports_long_names(root) else 240 length = 240
length -= len(root) length -= len(root)
if length < 5: if length < 5:
raise ValueError('%r is too long.'%root) raise ValueError('%r is too long.'%root)

View File

@ -11,7 +11,7 @@ from math import ceil
from calibre import force_unicode, isbytestring, prints, sanitize_file_name from calibre import force_unicode, isbytestring, prints, sanitize_file_name
from calibre.constants import ( from calibre.constants import (
filesystem_encoding, iswindows, plugins, preferred_encoding filesystem_encoding, iswindows, plugins, preferred_encoding, isosx
) )
from calibre.utils.localization import get_udc from calibre.utils.localization import get_udc
@ -38,18 +38,6 @@ def ascii_filename(orig, substitute='_'):
return sanitize_file_name(''.join(ans), substitute=substitute) return sanitize_file_name(''.join(ans), substitute=substitute)
def supports_long_names(path):
t = ('a'*300)+'.txt'
try:
p = os.path.join(path, t)
open(p, 'wb').close()
os.remove(p)
except:
return False
else:
return True
def shorten_component(s, by_what): def shorten_component(s, by_what):
l = len(s) l = len(s)
if l < by_what: if l < by_what:
@ -60,7 +48,24 @@ def shorten_component(s, by_what):
return s[:l] + s[-l:] return s[:l] + s[-l:]
def limit_component(x, limit=254):
# windows and macs use ytf-16 codepoints for length, linux uses arbitrary
# binary data, but we will assume utf-8
filename_encoding_for_length = 'utf-16' if iswindows or isosx else 'utf-8'
def encoded_length():
q = x if isinstance(x, bytes) else x.encode(filename_encoding_for_length)
return len(q)
while encoded_length() > limit:
delta = encoded_length() - limit
x = shorten_component(x, max(2, delta // 2))
return x
def shorten_components_to(length, components, more_to_take=0, last_has_extension=True): def shorten_components_to(length, components, more_to_take=0, last_has_extension=True):
components = [limit_component(cx) for cx in components]
filepath = os.sep.join(components) filepath = os.sep.join(components)
extra = len(filepath) - (length - more_to_take) extra = len(filepath) - (length - more_to_take)
if extra < 1: if extra < 1: