~fabrixxm/confy

ref: 0.6.0 confy/src/fetcher.py -rw-r--r-- 3.5 KiB
3659db31fabrixxm Confy 0.6.0 5 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# fetcher.py
#
# Copyright 2020 Fabio
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from threading import Thread
import os
import tempfile
import urllib.request

from gi.repository import GLib
from gi.repository import GObject


class Fetcher(GObject.GObject):
    """ Fetch source to dest, asyncly.

    property
        running (bool, r) download is running
        fraction (floar, r) download progress 0.0 - 1.0

    signals:
        error (message:str)
        done (local_file_path:str)
    """

    @GObject.Signal(arg_types=(str,))
    def error(self, message):
        ...

    @GObject.Signal(arg_types=(str,))
    def done(self, local_file_path):
        ...

    @GObject.property
    def running(self):
        return self._run

    @GObject.property
    def fraction(self):
        return self._fraction


    def __init__(self, source, dest=None, cbk=None):
        """ download `source` to `dest`
            `source`: uri
            `dest`: path. if None, then temp
            `cbk` : function called when done: `fn(local_file_path)`

            `cbk` is called before firing `done` signal. If any exception
            is raised by `cbk`, `error` signal is fired, instead.
        """
        super().__init__()

        self._run = True
        self._fraction = 0.0
        self._cbk = cbk

        if dest is None:
            _, dest = tempfile.mkstemp()
        Thread(target=self._fetch, args=(source, dest)).start()

    def _fetch(self, source, dest):
        try:
            with urllib.request.urlopen(source, timeout=20) as fsrc, open(dest, "wb") as fdst:
                total_size = int(fsrc.info().get('Content-Length', 0))
                block_size = 8192
                dwl_size = 0
                # python in runtime is old :)
                #while chunk := fsrc.read(block_size):
                chunk = fsrc.read(block_size)
                while chunk:
                    if not self._run:
                        raise DownloadCancelled("Download cacelled")
                    fdst.write(chunk)
                    if total_size > 0:
                        dwl_size += block_size
                        self._fraction = min(dwl_size, total_size) / total_size
                        GLib.idle_add(self.notify,"fraction")
                    chunk = fsrc.read(block_size)
        except Exception as e:
            os.unlink(dest)
            import traceback;  traceback.print_exc()
            GLib.idle_add(self.emit, "error", str(e))
        else:
            GLib.idle_add(self._done, dest)

    def _done(self, dest):
        if self._cbk is not None:
            try:
                self._cbk(self, dest)
            except Exception as e:
                import traceback;  traceback.print_exc()
                GLib.idle_add(self.emit, "error", str(e))
                return
        GLib.idle_add(self.emit, "done", dest)

    def cancel(self):
        self._run = False
        self.notify("running")