-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
api_gdrive.py
359 lines (298 loc) · 12.9 KB
/
api_gdrive.py
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
import os
import glob
import json
import mimetypes
from aiogoogle import Aiogoogle
class AsyncDriveUploader:
def __init__(self, credentials_path):
"""
Initialize Aiogoogle with user credentials
Args:
credentials_path (str): Path to OAuth 2.0 credentials file
"""
with open(credentials_path, "r", encoding="utf-8") as json_file:
creds_data = json.load(json_file)
self.user = {"access_token": creds_data['token'], "refresh_token": creds_data['refresh_token']}
self.client = {"client_id": creds_data['client_id'], "client_secret": creds_data['client_secret'], "scopes": creds_data['scopes']}
async def get_or_create_folder(self, folder_name, parent_folder_id=None):
"""
Find or create a folder in Google Drive.
Args:
folder_name (str): Name of the folder to find or create
parent_folder_id (str, optional): ID of parent folder
Returns:
str: Folder ID
"""
async with Aiogoogle(user_creds=self.user, client_creds=self.client) as aiogoogle:
drive_v3 = await aiogoogle.discover('drive', 'v3')
# Build query to find folder
query = [f"name='{folder_name}'", "mimeType='application/vnd.google-apps.folder'"]
if parent_folder_id:
query.append(f"'{parent_folder_id}' in parents")
# Search for existing folder
results = await aiogoogle.as_user(
drive_v3.files.list(
q=' and '.join(query),
spaces='drive'
)
)
folders = results.get('files', [])
# If folder exists, return its ID
if folders:
return folders[0]['id']
# If folder doesn't exist, create it
folder_metadata = {
'name': folder_name,
'mimeType': 'application/vnd.google-apps.folder'
}
# Add parent if specified
if parent_folder_id:
folder_metadata['parents'] = [parent_folder_id]
folder = await aiogoogle.as_user(
drive_v3.files.create(
json=folder_metadata,
fields='id'
)
)
return folder['id']
async def make_public_with_link(self, file_or_folder_id):
"""
Make a file or folder publicly accessible and get a shareable link.
Args:
file_or_folder_id (str): ID of the file or folder
Returns:
str: Public sharing link
"""
async with Aiogoogle(user_creds=self.user, client_creds=self.client) as aiogoogle:
drive_v3 = await aiogoogle.discover('drive', 'v3')
try:
# Create a public permission
await aiogoogle.as_user(
drive_v3.permissions.create(
fileId=file_or_folder_id,
json={'type': 'anyone', 'role': 'reader'}
)
)
# Get the file/folder details to retrieve the web view link
file_metadata = await aiogoogle.as_user(
drive_v3.files.get(
fileId=file_or_folder_id,
fields='webViewLink'
)
)
return file_metadata.get('webViewLink', None)
except Exception as e:
print(f"Error making file/folder public: {e}")
return None
async def batch_upload(self, paths, folder_in_drive='Uploaded', make_public=False, recursive=False):
"""
Batch upload multiple files and folders.
Args:
paths (list): List of file or folder paths to upload
folder_in_drive (str, optional): Parent folder name in Drive
make_public (bool, optional): Whether to make content public
recursive (bool, optional): Whether to recursively upload folder contents
Returns:
list: List of upload results
"""
# Get or create the root folder in Drive
root_folder_id = await self.get_or_create_folder(folder_in_drive)
# Results storage
upload_results = []
# Process each path
for path in paths:
# Expand glob patterns
matching_paths = glob.glob(path)
for matched_path in matching_paths:
try:
# Check if path exists
if not os.path.exists(matched_path):
print(f"Path not found: {matched_path}")
continue
# Determine upload method based on path type
if os.path.isfile(matched_path):
# Upload single file
result = await self._upload_single_file(
matched_path,
root_folder_id,
make_public
)
upload_results.append(result)
elif os.path.isdir(matched_path):
# Upload folder
if recursive:
result = await self._upload_folder(
matched_path,
root_folder_id,
make_public
)
else:
# Non-recursive folder upload (only files in root)
result = await self._upload_non_recursive_folder(
matched_path,
root_folder_id,
make_public
)
upload_results.append(result)
except Exception as e:
print(f"Error uploading {matched_path}: {e}")
return upload_results
async def _upload_single_file(self, file_path, parent_folder_id=None, make_public=False):
"""
Upload a single file to Google Drive.
Args:
file_path (str): Path to the file to upload
parent_folder_id (str, optional): ID of parent folder
make_public (bool, optional): Whether to make the file public
Returns:
dict: Information about uploaded file
"""
async with Aiogoogle(user_creds=self.user, client_creds=self.client) as aiogoogle:
drive_v3 = await aiogoogle.discover('drive', 'v3')
# Get file size
file_size = os.path.getsize(file_path)
# Prepare file metadata
file_metadata = {
'name': os.path.basename(file_path)
}
# Add parent folder if specified
if parent_folder_id:
file_metadata['parents'] = [parent_folder_id]
# Detect MIME type
mime_type, _ = mimetypes.guess_type(file_path)
if mime_type is None:
mime_type = 'application/octet-stream'
# Upload the file
print(file_path)
file = await aiogoogle.as_user(
drive_v3.files.create(
json=file_metadata,
upload_file=file_path,
fields='id,webViewLink'
)
)
# Make file public if requested
public_link = None
if make_public:
public_link = await self.make_public_with_link(file['id'])
return {
'type': 'file',
'name': os.path.basename(file_path),
'full_path': file_path,
'id': file['id'],
'size_bytes': file_size,
'size_human_readable': self._format_file_size(file_size),
'link': public_link or file.get('webViewLink')
}
async def _upload_folder(self, local_path, parent_folder_id=None, make_public=False):
"""
Recursively upload a local folder to Google Drive.
Args:
local_path (str): Path to local folder to upload
parent_folder_id (str, optional): ID of parent folder in Drive
make_public (bool, optional): Whether to make the folder public
Returns:
dict: Information about uploaded folder
"""
# Get the base folder name
folder_name = os.path.basename(local_path)
# Create the folder in Google Drive
current_folder_id = await self.get_or_create_folder(
folder_name,
parent_folder_id
)
# Make folder public if requested
public_link = None
if make_public:
public_link = await self.make_public_with_link(current_folder_id)
# Track uploaded files
uploaded_files = []
total_size = 0
# Iterate through all files and subdirectories
for item in os.listdir(local_path):
local_item_path = os.path.join(local_path, item)
# Recursively upload files and subdirectories
if os.path.isfile(local_item_path):
file_result = await self._upload_single_file(
local_item_path,
current_folder_id,
make_public
)
total_size += file_result.get('size_bytes', 0)
uploaded_files.append(file_result)
elif os.path.isdir(local_item_path):
subfolder_result = await self._upload_folder(
local_item_path,
current_folder_id,
make_public
)
total_size += subfolder_result.get('size_bytes', 0)
uploaded_files.append(subfolder_result)
return {
'type': 'folder',
'folder_id': current_folder_id,
'name': folder_name,
'full_path': local_path,
'link': public_link,
'size_bytes': total_size,
'size_human_readable': self._format_file_size(total_size),
'files': uploaded_files
}
async def _upload_non_recursive_folder(self, local_path, parent_folder_id=None, make_public=False):
"""
Upload only files in the root of a folder (non-recursive).
Args:
local_path (str): Path to local folder to upload
parent_folder_id (str, optional): ID of parent folder in Drive
make_public (bool, optional): Whether to make the folder public
Returns:
dict: Information about uploaded folder
"""
# Get the base folder name
folder_name = os.path.basename(local_path)
# Create the folder in Google Drive
current_folder_id = await self.get_or_create_folder(
folder_name,
parent_folder_id
)
# Make folder public if requested
public_link = None
if make_public:
public_link = await self.make_public_with_link(current_folder_id)
# Track uploaded files
uploaded_files = []
total_size = 0
# Upload only files in the root of the folder
for item in os.listdir(local_path):
local_item_path = os.path.join(local_path, item)
# Upload only files, skip subdirectories
if os.path.isfile(local_item_path):
file_result = await self._upload_single_file(
local_item_path,
current_folder_id,
make_public
)
total_size += file_result.get('size_bytes', 0)
uploaded_files.append(file_result)
return {
'type': 'folder',
'folder_id': current_folder_id,
'name': folder_name,
'full_path': local_path,
'link': public_link,
'size_bytes': total_size,
'size_human_readable': self._format_file_size(total_size),
'files': uploaded_files
}
def _format_file_size(self, size_bytes):
"""
Convert file size in bytes to human-readable format.
Args:
size_bytes (int): Size in bytes
Returns:
str: Human-readable size
"""
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
if size_bytes < 1024.0:
return f"{size_bytes:.2f} {unit}"
size_bytes /= 1024.0