74
76
parent_folder_id = folder['id']
75
77
self.folder = parent_folder_id
78
return self.drive.ListFile({'q': "'" + self.folder + "' in parents and trashed=false"}).GetList()
80
def file_by_name(self, filename):
81
from pydrive.files import ApiRequestError
82
if filename in self.id_cache:
83
# It might since have been locally moved, renamed or deleted, so we
84
# need to validate the entry.
85
file_id = self.id_cache[filename]
86
drive_file = self.drive.CreateFile({'id': file_id})
88
if drive_file['title'] == filename and not drive_file['labels']['trashed']:
89
for parent in drive_file['parents']:
90
if parent['id'] == self.folder:
91
log.Info("PyDrive backend: found file '%s' with id %s in ID cache" % (filename, file_id))
93
except ApiRequestError as error:
94
# A 404 occurs if the ID is no longer valid
95
if error.args[0].resp.status != 404:
97
# If we get here, the cache entry is invalid
98
log.Info("PyDrive backend: invalidating '%s' (previously ID %s) from ID cache" % (filename, file_id))
99
del self.id_cache[filename]
101
# Not found in the cache, so use directory listing. This is less
102
# reliable because there is no strong consistency.
103
q = "title='%s' and '%s' in parents and trashed=false" % (filename, self.folder)
104
fields = 'items(title,id,fileSize,downloadUrl,exportLinks),nextPageToken'
105
flist = self.drive.ListFile({'q': q, 'fields': fields}).GetList()
107
log.FatalError(_("PyDrive backend: multiple files called '%s'.") % (filename,))
109
file_id = flist[0]['id']
110
self.id_cache[filename] = flist[0]['id']
111
log.Info("PyDrive backend: found file '%s' with id %s on server, adding to cache" % (filename, file_id))
113
log.Info("PyDrive backend: file '%s' not found in cache or on server" % (filename,))
80
116
def id_by_name(self, filename):
82
return next(item for item in self.FilesList() if item['title'] == filename)['id']
117
drive_file = self.file_by_name(filename)
118
if drive_file is None:
121
return drive_file['id']
86
123
def _put(self, source_path, remote_filename):
87
drive_file = self.drive.CreateFile({'title': remote_filename, 'parents': [{"kind": "drive#fileLink", "id": self.folder}]})
124
drive_file = self.file_by_name(remote_filename)
125
if drive_file is None:
126
# No existing file, make a new one
127
drive_file = self.drive.CreateFile({'title': remote_filename, 'parents': [{"kind": "drive#fileLink", "id": self.folder}]})
128
log.Info("PyDrive backend: creating new file '%s'" % (remote_filename,))
130
log.Info("PyDrive backend: replacing existing file '%s' with id '%s'" % (
131
remote_filename, drive_file['id']))
88
132
drive_file.SetContentFile(source_path.name)
89
133
drive_file.Upload()
134
self.id_cache[remote_filename] = drive_file['id']
91
136
def _get(self, remote_filename, local_path):
92
drive_file = self.drive.CreateFile({'id': self.id_by_name(remote_filename)})
137
drive_file = self.file_by_name(remote_filename)
93
138
drive_file.GetContentFile(local_path.name)
96
return [item['title'] for item in self.FilesList()]
141
drive_files = self.drive.ListFile({
142
'q': "'" + self.folder + "' in parents and trashed=false",
143
'fields': 'items(title,id),nextPageToken'}).GetList()
144
filenames = set(item['title'] for item in drive_files)
145
# Check the cache as well. A file might have just been uploaded but
146
# not yet appear in the listing.
147
# Note: do not use iterkeys() here, because file_by_name will modify
148
# the cache if it finds invalid entries.
149
for filename in self.id_cache.keys():
150
if (filename not in filenames) and (self.file_by_name(filename) is not None):
151
filenames.add(filename)
152
return list(filenames)
98
154
def _delete(self, filename):
99
155
file_id = self.id_by_name(filename)
100
drive_file = self.drive.CreateFile({'id': file_id})
101
drive_file.auth.service.files().delete(fileId=drive_file['id']).execute()
103
def _delete_list(self, filename_list):
104
to_remove = set(filename_list)
105
for item in self.FilesList():
106
if item['title'] not in to_remove:
109
drive_file = self.drive.CreateFile({'id': file_id})
110
drive_file.auth.service.files().delete(fileId=drive_file['id']).execute()
157
self.drive.auth.service.files().delete(fileId=file_id).execute()
159
log.Warn("File '%s' does not exist while trying to delete it" % (filename,))
112
161
def _query(self, filename):
114
size = int((item for item in self.FilesList() if item['title'] == filename).next()['fileSize'])
162
drive_file = self.file_by_name(filename)
163
if drive_file is None:
166
size = int(drive_file['fileSize'])
117
167
return {'size': size}
169
def _error_code(self, operation, error):
170
from pydrive.files import ApiRequestError, FileNotUploadedError
171
if isinstance(error, FileNotUploadedError):
172
return log.ErrorCode.backend_not_found
173
elif isinstance(error, ApiRequestError):
174
http_status = error.args[0].resp.status
175
if http_status == 404:
176
return log.ErrorCode.backend_not_found
177
elif http_status == 403:
178
return log.ErrorCode.backend_permission_denied
179
return log.ErrorCode.backend_error
119
181
duplicity.backend.register_backend('pydrive', PyDriveBackend)
120
182
""" pydrive is an alternate way to access gdocs """
121
183
duplicity.backend.register_backend('pydrive+gdocs', PyDriveBackend)