1"""
2pgxnclient -- network interaction
3"""
4
5# Copyright (C) 2011-2021 Daniele Varrazzo
6
7# This file is part of the PGXN client
8
9import os
10from six.moves.urllib.request import build_opener
11from six.moves.urllib.error import HTTPError, URLError
12from six.moves.urllib.parse import urlsplit
13from itertools import count
14from contextlib import closing
15
16from pgxnclient import __version__
17from pgxnclient.i18n import _
18from pgxnclient.errors import (
19    PgxnClientException,
20    NetworkError,
21    ResourceNotFound,
22    BadRequestError,
23)
24
25import logging
26
27logger = logging.getLogger('pgxnclient.network')
28
29
30def get_file(url):
31    opener = build_opener()
32    opener.addheaders = [('User-agent', 'pgxnclient/%s' % __version__)]
33    logger.debug('opening url: %s', url)
34    try:
35        return closing(opener.open(url))
36    except HTTPError as e:
37        if e.code == 404:
38            raise ResourceNotFound(_("resource not found: '%s'") % e.url)
39        elif e.code == 400:
40            raise BadRequestError(_("bad request on '%s'") % e.url)
41        elif e.code == 500:
42            raise NetworkError(_("server error"))
43        elif e.code == 503:
44            raise NetworkError(_("service unavailable"))
45        else:
46            raise NetworkError(
47                _("unexpected response %d for '%s'") % (e.code, e.url)
48            )
49    except URLError as e:
50        raise NetworkError(_("network error: %s") % e.reason)
51
52
53def get_local_file_name(target, url):
54    """Return a good name for a local file.
55
56    If *target* is a dir, make a name out of the url. Otherwise return target
57    itself. Always return an absolute path.
58    """
59    if os.path.isdir(target):
60        basename = urlsplit(url)[2].rsplit('/', 1)[-1]
61        fn = os.path.join(target, basename)
62    else:
63        fn = target
64
65    return os.path.abspath(fn)
66
67
68def download(f, fn, rename=True):
69    """Download a file locally.
70
71    :param f: open file to read
72    :param fn: name of the file to write. If a dir, save into it.
73    :param rename: if true and a file *fn* exist, rename the downloaded file
74        adding a prefix ``-1``, ``-2``... before the extension.
75
76    Return the name of the file saved.
77    """
78    if os.path.isdir(fn):
79        fn = get_local_file_name(fn, f.url)
80
81    if rename:
82        if os.path.exists(fn):
83            base, ext = os.path.splitext(fn)
84            for i in count(1):
85                logger.debug(_("file %s exists"), fn)
86                fn = "%s-%d%s" % (base, i, ext)
87                if not os.path.exists(fn):
88                    break
89
90    logger.info(_("saving %s"), fn)
91    try:
92        fout = open(fn, "wb")
93    except Exception as e:
94        raise PgxnClientException(
95            _("cannot open target file: %s: %s") % (e.__class__.__name__, e)
96        )
97    try:
98        while 1:
99            data = f.read(8192)
100            if not data:
101                break
102            fout.write(data)
103    finally:
104        fout.close()
105
106    return fn
107