Skip to content

Commit

Permalink
Fixed #451 - OSError: [Errno 36] File name too long
Browse files Browse the repository at this point in the history
  • Loading branch information
jkbrzt committed Mar 17, 2016
1 parent 001bda1 commit 5300b0b
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ This project adheres to `Semantic Versioning <http://semver.org/>`_.
of light and dark terminals
* Improved ``--debug`` output
* 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


Expand Down
36 changes: 34 additions & 2 deletions httpie/downloads.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import os
import re
import sys
import errno
import mimetypes
import threading
from time import sleep, time
Expand Down Expand Up @@ -135,12 +136,43 @@ def filename_from_url(url, content_type):
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):
attempt = 0
while True:
suffix = '-' + str(attempt) if attempt > 0 else ''
if not exists(filename + suffix):
return filename + suffix
try_filename = trim_filename_if_needed(filename, extra=len(suffix))
try_filename += suffix
if not exists(try_filename):
return try_filename
attempt += 1


Expand Down
34 changes: 30 additions & 4 deletions tests/test_downloads.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import time

import pytest
import mock
from requests.structures import CaseInsensitiveDict

from httpie.compat import urlopen
Expand Down Expand Up @@ -74,7 +75,31 @@ def test_filename_from_url(self):
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):
# noinspection PyUnresolvedReferences,PyUnusedLocal
def exists(filename):
Expand All @@ -86,9 +111,10 @@ def exists(filename):
exists.attempt = 0
return exists

assert 'foo.bar' == get_unique_filename('foo.bar', attempts(0))
assert 'foo.bar-1' == get_unique_filename('foo.bar', attempts(1))
assert 'foo.bar-10' == get_unique_filename('foo.bar', attempts(10))
get_filename_max_length.return_value = 10

actual = get_unique_filename(orig_name, attempts(unique_on_attempt))
assert expected == actual


class TestDownloads:
Expand Down

0 comments on commit 5300b0b

Please sign in to comment.