Fixed #451 - OSError: [Errno 36] File name too long

This commit is contained in:
Jakub Roztocil 2016-03-17 15:58:01 +08:00
parent 001bda1945
commit 5300b0b490
3 changed files with 65 additions and 6 deletions

View File

@ -27,6 +27,7 @@ This project adheres to `Semantic Versioning <http://semver.org/>`_.
of light and dark terminals of light and dark terminals
* Improved ``--debug`` output * Improved ``--debug`` output
* Fixed ``--session`` when used with ``--download`` * Fixed ``--session`` when used with ``--download``
* Fixed ``--download`` to trim too long filenames before saving the file
* Fixed handling of ``Content-Type`` with multiple ``+subtype`` parts * Fixed handling of ``Content-Type`` with multiple ``+subtype`` parts

View File

@ -7,6 +7,7 @@ from __future__ import division
import os import os
import re import re
import sys import sys
import errno
import mimetypes import mimetypes
import threading import threading
from time import sleep, time from time import sleep, time
@ -135,12 +136,43 @@ def filename_from_url(url, content_type):
return fn return fn
def trim_filename(filename, max_len):
if len(filename) > max_len:
trim_by = len(filename) - max_len
name, ext = os.path.splitext(filename)
if trim_by >= len(name):
filename = filename[:-trim_by]
else:
filename = name[:-trim_by] + ext
return filename
def get_filename_max_length(directory):
try:
max_len = os.pathconf(directory, 'PC_NAME_MAX')
except OSError as e:
if e.errno == errno.EINVAL:
max_len = 255
else:
raise
return max_len
def trim_filename_if_needed(filename, directory='.', extra=0):
max_len = get_filename_max_length(directory) - extra
if len(filename) > max_len:
filename = trim_filename(filename, max_len)
return filename
def get_unique_filename(filename, exists=os.path.exists): def get_unique_filename(filename, exists=os.path.exists):
attempt = 0 attempt = 0
while True: while True:
suffix = '-' + str(attempt) if attempt > 0 else '' suffix = '-' + str(attempt) if attempt > 0 else ''
if not exists(filename + suffix): try_filename = trim_filename_if_needed(filename, extra=len(suffix))
return filename + suffix try_filename += suffix
if not exists(try_filename):
return try_filename
attempt += 1 attempt += 1

View File

@ -2,6 +2,7 @@ import os
import time import time
import pytest import pytest
import mock
from requests.structures import CaseInsensitiveDict from requests.structures import CaseInsensitiveDict
from httpie.compat import urlopen from httpie.compat import urlopen
@ -74,7 +75,31 @@ class TestDownloadUtils:
content_type='x-foo/bar' content_type='x-foo/bar'
) )
def test_unique_filename(self): @pytest.mark.parametrize(
'orig_name, unique_on_attempt, expected',
[
# Simple
('foo.bar', 0, 'foo.bar'),
('foo.bar', 1, 'foo.bar-1'),
('foo.bar', 10, 'foo.bar-10'),
# Trim
('A' * 20, 0, 'A' * 10),
('A' * 20, 1, 'A' * 8 + '-1'),
('A' * 20, 10, 'A' * 7 + '-10'),
# Trim before ext
('A' * 20 + '.txt', 0, 'A' * 6 + '.txt'),
('A' * 20 + '.txt', 1, 'A' * 4 + '.txt-1'),
# Trim at the end
('foo.' + 'A' * 20, 0, 'foo.' + 'A' * 6),
('foo.' + 'A' * 20, 1, 'foo.' + 'A' * 4 + '-1'),
('foo.' + 'A' * 20, 10, 'foo.' + 'A' * 3 + '-10'),
]
)
@mock.patch('httpie.downloads.get_filename_max_length')
def test_unique_filename(self, get_filename_max_length,
orig_name, unique_on_attempt,
expected):
def attempts(unique_on_attempt=0): def attempts(unique_on_attempt=0):
# noinspection PyUnresolvedReferences,PyUnusedLocal # noinspection PyUnresolvedReferences,PyUnusedLocal
def exists(filename): def exists(filename):
@ -86,9 +111,10 @@ class TestDownloadUtils:
exists.attempt = 0 exists.attempt = 0
return exists return exists
assert 'foo.bar' == get_unique_filename('foo.bar', attempts(0)) get_filename_max_length.return_value = 10
assert 'foo.bar-1' == get_unique_filename('foo.bar', attempts(1))
assert 'foo.bar-10' == get_unique_filename('foo.bar', attempts(10)) actual = get_unique_filename(orig_name, attempts(unique_on_attempt))
assert expected == actual
class TestDownloads: class TestDownloads: