~facundo/encuentro/trunk

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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# Copyright 2013 Facundo Batista
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, 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/>.
#
# For further info, check  https://launchpad.net/encuentro

from __future__ import print_function

"""Some useful functions."""

import defer
import os

from PyQt4 import QtNetwork, QtCore

_qt_network_manager = QtNetwork.QNetworkAccessManager()


class _Downloader(object):
    """An asynch downloader that fires a deferred with data when done."""
    def __init__(self, url):
        self.deferred = defer.Deferred()
        self.deferred._store_it_because_qt_needs_or_wont_work = self
        self.progress = 0
        request = QtNetwork.QNetworkRequest(QtCore.QUrl(url))

        self.req = _qt_network_manager.get(request)
        self.req.error.connect(self.error)
        self.req.finished.connect(self.end)
        self.req.downloadProgress.connect(self._advance_progress)

    def error(self, error_code):
        """Request finished (*maybe*) on error."""
        if error_code != 5:
            # different to OperationCanceledError, so we didn't provoke it
            exc = RuntimeError("Network Error: " + self.req.errorString())
            self.deferred.errback(exc)

    def _advance_progress(self, dloaded, total):
        """Increment progress."""
        self.progress = dloaded

    def abort(self):
        """Abort the download."""
        self.req.abort()

    def end(self):
        """Send data through the deferred, if wasn't fired before."""
        img_data = self.req.read(self.req.bytesAvailable())
        content_type = self.req.header(
            QtNetwork.QNetworkRequest.ContentTypeHeader)
        data = (content_type, img_data)
        if not self.deferred.called:
            self.deferred.callback(data)


def download(url):
    """Deferredly download an URL, non blocking.

    It starts a _Downloader, and supervises if it stalled. If didn't transfer
    anything for a whole second, abort it and create a new one. This is to overcome
    some QtNetwork weirdness that will probably go away in future Qt versions, but
    that froze the downloading somewhen somehow.
    """
    general_deferred = defer.Deferred()
    state = [_Downloader(url), 0]

    def check():
        dloader, prev_prog = state

        if dloader.deferred.called:
            # finished, passthrough the results
            dloader.deferred.add_callbacks(general_deferred.callback, general_deferred.errback)
        else:
            if dloader.progress == prev_prog:
                # stalled! need to restart it
                dloader.abort()
                state[0] = _Downloader(url)
                state[1] = 0
            else:
                # keep going, update progress
                state[1] = dloader.progress

            # for both cases, new downloader or old still running, check later
            QtCore.QTimer.singleShot(1000, check)

    QtCore.QTimer.singleShot(1000, check)
    return general_deferred


class SafeSaver(object):
    """A safe saver to disk.

    It saves to a .tmp and moves into final destination, and other
    considerations.
    """

    def __init__(self, fname):
        self.fname = fname
        self.tmp = fname + ".tmp"
        self.fh = None

    def __enter__(self):
        self.fh = open(self.tmp, 'wb')
        return self.fh

    def __exit__(self, *exc_data):
        self.fh.close()

        # only move into final destination if all went ok
        if exc_data == (None, None, None):
            if os.path.exists(self.fname):
                # in Windows we need to remove the old file first
                os.remove(self.fname)
            os.rename(self.tmp, self.fname)


if __name__ == "__main__":
    import sys
    app = QtCore.QCoreApplication(sys.argv)
    _url = "http://www.taniquetil.com.ar/facundo/imgs/felu-camagrande.jpg"

    @defer.inline_callbacks
    def _download():
        """Download."""
        deferred = download(_url)
        data = yield deferred
        print("All done!", len(data), type(data))
    _download()
    sys.exit(app.exec_())