~duplicity-team/duplicity/0.7-series

« back to all changes in this revision

Viewing changes to duplicity/backends/onedrivebackend.py

  • Committer: kenneth at loafman
  • Date: 2019-06-11 15:52:04 UTC
  • mfrom: (1372.2.8 0-7-snap-duplicity)
  • Revision ID: kenneth@loafman.com-20190611155204-ck3otzhy9d3x8kxv
* Merged in lp:~aaron-whitehouse/duplicity/07-snap
  - Add snapcraft packaging instructions for 0.7 series

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
import json
26
26
import os
27
27
import sys
28
 
# On debian (and derivatives), get these dependencies using:
29
 
# apt-get install python-requests python-requests-oauthlib
30
 
# On fedora (and derivatives), get these dependencies using:
31
 
# yum install python-requests python-requests-oauthlib
32
 
import requests
33
 
from requests_oauthlib import OAuth2Session
34
28
 
35
29
import duplicity.backend
36
30
from duplicity.errors import BackendException
41
35
# http://msdn.microsoft.com/en-us/library/dn659752.aspx
42
36
# http://msdn.microsoft.com/en-us/library/dn631844.aspx
43
37
# https://gist.github.com/rgregg/37ba8929768a62131e85
 
38
 
 
39
 
44
40
class OneDriveBackend(duplicity.backend.Backend):
45
41
    """Uses Microsoft OneDrive (formerly SkyDrive) for backups."""
46
42
 
63
59
 
64
60
    def __init__(self, parsed_url):
65
61
        duplicity.backend.Backend.__init__(self, parsed_url)
 
62
 
 
63
        # Import requests and requests-oauthlib
 
64
        try:
 
65
            # On debian (and derivatives), get these dependencies using:
 
66
            # apt-get install python-requests python-requests-oauthlib
 
67
            # On fedora (and derivatives), get these dependencies using:
 
68
            # yum install python-requests python-requests-oauthlib
 
69
            global requests
 
70
            global OAuth2Session
 
71
            import requests
 
72
            from requests_oauthlib import OAuth2Session
 
73
        except ImportError as e:
 
74
            raise BackendException((
 
75
                'OneDrive backend requires python-requests and '
 
76
                'python-requests-oauthlib to be installed. Please install '
 
77
                'them and try again.\n' + str(e)))
 
78
 
66
79
        self.names_to_ids = None
67
80
        self.user_id = None
68
81
        self.directory = parsed_url.path.lstrip('/')
98
111
                       'Trying to create a new one. (original error: %s)' % e))
99
112
 
100
113
        self.http_client = OAuth2Session(
101
 
                self.CLIENT_ID,
102
 
                scope=self.OAUTH_SCOPE,
103
 
                redirect_uri=self.OAUTH_REDIRECT_URI,
104
 
                token=token,
105
 
                auto_refresh_kwargs={
106
 
                    'client_id': self.CLIENT_ID,
107
 
                    'client_secret': self.CLIENT_SECRET,
108
 
                },
109
 
                auto_refresh_url=self.OAUTH_TOKEN_URI,
110
 
                token_updater=token_updater)
 
114
            self.CLIENT_ID,
 
115
            scope=self.OAUTH_SCOPE,
 
116
            redirect_uri=self.OAUTH_REDIRECT_URI,
 
117
            token=token,
 
118
            auto_refresh_kwargs={
 
119
                'client_id': self.CLIENT_ID,
 
120
                'client_secret': self.CLIENT_SECRET,
 
121
            },
 
122
            auto_refresh_url=self.OAUTH_TOKEN_URI,
 
123
            token_updater=token_updater)
 
124
 
 
125
        # We have to refresh token manually because it's not working "under the covers"
 
126
        if token is not None:
 
127
            self.http_client.refresh_token(self.OAUTH_TOKEN_URI)
111
128
 
112
129
        # Send a request to make sure the token is valid (or could at least be
113
130
        # refreshed successfully, which will happen under the covers). In case
124
141
                                'interactively, so duplicity cannot possibly '
125
142
                                'access OneDrive.' % self.OAUTH_TOKEN_PATH))
126
143
            authorization_url, state = self.http_client.authorization_url(
127
 
                    self.OAUTH_AUTHORIZE_URI, display='touch')
128
 
 
129
 
            print ''
130
 
            print ('In order to authorize duplicity to access your OneDrive, '
131
 
                   'please open %s in a browser and copy the URL of the blank '
132
 
                   'page the dialog leads to.' % authorization_url)
133
 
            print ''
134
 
 
135
 
            redirected_to = raw_input('URL of the blank page: ')
136
 
 
137
 
            token = self.http_client.fetch_token(self.OAUTH_TOKEN_URI,
 
144
                self.OAUTH_AUTHORIZE_URI, display='touch')
 
145
 
 
146
            print()
 
147
            print('In order to authorize duplicity to access your OneDrive, '
 
148
                  'please open %s in a browser and copy the URL of the blank '
 
149
                  'page the dialog leads to.' % authorization_url)
 
150
            print()
 
151
 
 
152
            redirected_to = raw_input('URL of the blank page: ').strip()
 
153
 
 
154
            token = self.http_client.fetch_token(
 
155
                self.OAUTH_TOKEN_URI,
138
156
                client_secret=self.CLIENT_SECRET,
139
157
                authorization_response=redirected_to)
140
158
 
151
169
                           'Original error: %s' % (
152
170
                               self.OAUTH_TOKEN_PATH, e)))
153
171
 
154
 
        if not 'id' in user_info_response.json():
 
172
        if 'id' not in user_info_response.json():
155
173
            log.Error('user info response lacks the "id" field.')
156
174
 
157
175
        self.user_id = user_info_response.json()['id']
158
176
 
159
177
    def resolve_directory(self):
160
178
        """Ensures self.directory_id contains the folder id for the path.
161
 
        
 
179
 
162
180
        There is no API call to resolve a logical path (e.g.
163
181
        /backups/duplicity/notebook/), so we recursively list directories
164
182
        until we get the object id of the configured directory, creating
165
183
        directories as necessary.
166
184
        """
167
185
        object_id = 'me/skydrive'
168
 
        for component in self.directory.split('/'):
 
186
        for component in [x for x in self.directory.split('/') if x]:
169
187
            tried_mkdir = False
170
188
            while True:
171
189
                files = self.get_files(object_id)
176
194
                        tried_mkdir = True
177
195
                        continue
178
196
                    raise BackendException((
179
 
                            'Could not resolve/create directory "%s" on '
180
 
                            'OneDrive: %s not in %s (files of folder %s)' % (
181
 
                                self.directory, component,
182
 
                                names_to_ids.keys(), object_id)))
 
197
                        'Could not resolve/create directory "%s" on '
 
198
                        'OneDrive: %s not in %s (files of folder %s)' % (
 
199
                            self.directory, component,
 
200
                            names_to_ids.keys(), object_id)))
183
201
                break
184
202
            object_id = names_to_ids[component]
185
203
        self.directory_id = object_id
212
230
    def get_file_id(self, remote_filename):
213
231
        """Returns the file id from cache, updating the cache if necessary."""
214
232
        if (self.names_to_ids is None or
215
 
            remote_filename not in self.names_to_ids):
 
233
                remote_filename not in self.names_to_ids):
216
234
            self._list()
217
235
        return self.names_to_ids.get(remote_filename)
218
236
 
239
257
        response = self.http_client.get(self.API_URI + 'me/skydrive/quota')
240
258
        response.raise_for_status()
241
259
        if ('available' in response.json() and
242
 
            source_size > response.json()['available']):
 
260
                source_size > response.json()['available']):
243
261
            raise BackendException((
244
262
                'Out of space: trying to store "%s" (%d bytes), but only '
245
263
                '%d bytes available on OneDrive.' % (
263
281
                url,
264
282
                headers=headers)
265
283
            response.raise_for_status()
266
 
            if (not 'bits-packet-type' in response.headers or
267
 
                response.headers['bits-packet-type'].lower() != 'ack'):
 
284
            if ('bits-packet-type' not in response.headers or
 
285
                    response.headers['bits-packet-type'].lower() != 'ack'):
268
286
                raise BackendException((
269
287
                    'File "%s" cannot be uploaded: '
270
288
                    'Could not create BITS session: '
315
333
    def _query(self, remote_filename):
316
334
        file_id = self.get_file_id(remote_filename)
317
335
        if file_id is None:
318
 
            return {'size':-1}
 
336
            return {'size': -1}
319
337
        response = self.http_client.get(self.API_URI + file_id)
320
338
        response.raise_for_status()
321
339
        if 'size' not in response.json():