1
1
2
2
import os
3
3
import logging
4
+
4
5
from googleapiclient .discovery import build
5
6
from google .oauth2 .credentials import Credentials
6
7
from googleapiclient .http import MediaFileUpload
7
8
from google .auth .transport .requests import Request
8
9
from google_auth_oauthlib .flow import InstalledAppFlow
9
10
from benchmark_runner .common .logger .logger_time_stamp import logger # Added logger import
10
11
11
- # Configure logging
12
- logging .basicConfig (level = logging .INFO )
13
- logger = logging .getLogger (__name__ )
14
-
15
12
# Define the scope
16
13
SCOPES = ['https://www.googleapis.com/auth/drive' ]
17
14
@@ -23,31 +20,232 @@ class GoogleDriveOperations:
23
20
1. credentials.json - Obtain from the "Google Cloud Console" under "APIs & Services" > "Credentials" > "Create Credentials" and select "OAuth 2.0 Client ID".
24
21
2. token.json - This is generated automatically. If a browser is not available, copy the token from a machine with a browser.
25
22
"""
26
- def __init__ (self ):
23
+ def __init__ (self , google_drive_path : str , credentials_path : str , token_path : str , shared_drive_id : str ):
27
24
"""
28
25
Initializes GoogleDriveOperations with authentication.
29
26
"""
27
+ self ._google_drive_path = google_drive_path
28
+ self ._credentials_path = credentials_path
29
+ self ._token_path = token_path
30
+ self ._shared_drive_id = shared_drive_id
30
31
self .creds = self .authenticate ()
31
32
self .service = build ('drive' , 'v3' , credentials = self .creds )
32
33
33
34
def authenticate (self ):
34
35
"""
35
- Authenticates and returns credentials using token.json or interactive login.
36
+ Authenticates and returns credentials using token or interactive login.
36
37
:return: Credentials object
37
38
"""
38
39
creds = None
39
- if os .path .exists ('token.json' ):
40
- creds = Credentials .from_authorized_user_file ('token.json' , SCOPES )
40
+ if os .path .exists (self . _token_path ):
41
+ creds = Credentials .from_authorized_user_file (self . _token_path , SCOPES )
41
42
if not creds or not creds .valid :
42
43
if creds and creds .expired and creds .refresh_token :
43
44
creds .refresh (Request ())
44
45
else :
45
- flow = InstalledAppFlow .from_client_secrets_file ('credentials.json' , SCOPES )
46
+ flow = InstalledAppFlow .from_client_secrets_file (self . _credentials_path , SCOPES )
46
47
creds = flow .run_local_server (port = 0 )
47
- with open ('token.json' , 'w' ) as token_file :
48
+ with open (self . _token_path , 'w' ) as token_file :
48
49
token_file .write (creds .to_json ())
49
50
return creds
50
51
52
+ def list_folders_and_files_in_shared_drive (self , shared_drive_id ):
53
+ """
54
+ Lists all folders and files within a specified Google Drive shared drive.
55
+ :param shared_drive_id: The ID of the shared drive to list.
56
+ """
57
+ try :
58
+ logger .info (f'Folders and Files in shared drive with ID { shared_drive_id } :' )
59
+ self .list_files_in_folder (shared_drive_id )
60
+ logger .info ("Folder and file listing successful!" )
61
+ except Exception as e :
62
+ logger .error (f"An error occurred: { e } " )
63
+
64
+ def get_folder_id (self , folder_names , shared_drive_id ):
65
+ """
66
+ Finds the ID of a nested folder structure in the specified shared drive.
67
+ :param folder_names: A list of folder names to traverse.
68
+ :param shared_drive_id: The ID of the shared drive.
69
+ :return: The ID of the innermost folder if the full path exists, else None.
70
+ """
71
+ parent_id = shared_drive_id
72
+
73
+ for folder_name in folder_names :
74
+ query = f"'{ parent_id } ' in parents and mimeType='application/vnd.google-apps.folder' and name='{ folder_name } '"
75
+ response = self .service .files ().list (
76
+ q = query ,
77
+ supportsAllDrives = True ,
78
+ fields = 'files(id)' ,
79
+ spaces = 'drive'
80
+ ).execute ()
81
+
82
+ folders = response .get ('files' , [])
83
+ if folders :
84
+ # Folder exists, move to the next level
85
+ parent_id = folders [0 ]['id' ]
86
+ else :
87
+ # If any folder in the path doesn't exist, return None
88
+ return None
89
+
90
+ return parent_id
91
+
92
+ def create_folder_at_path (self , folder_path , parent_folder_id ):
93
+ """
94
+ Creates folders along a specified path in Google Drive and returns the ID of the last folder.
95
+ :param folder_path: The path of folders to create, e.g., '2024/09/15/10'.
96
+ :param parent_folder_id: The ID of the parent folder where the path starts.
97
+ :return: The ID of the last folder in the path if successful, otherwise None.
98
+ """
99
+ for folder_name in folder_path .split ('/' ):
100
+ parent_folder_id = self ._find_or_create_folder (folder_name , parent_folder_id )
101
+ if not parent_folder_id :
102
+ return None
103
+ return parent_folder_id
104
+
105
+ def _find_or_create_folder (self , folder_name , parent_folder_id ):
106
+ """
107
+ Finds a folder by name within a specified parent folder or creates it if not found.
108
+ :param folder_name: The name of the folder to find or create.
109
+ :param parent_folder_id: The ID of the parent folder to search within.
110
+ :return: The ID of the found or newly created folder.
111
+ """
112
+ folder_id = self ._find_folder_id (parent_folder_id , folder_name )
113
+ if folder_id :
114
+ return folder_id
115
+
116
+ # Folder does not exist, create it
117
+ return self .create_folder (folder_name , parent_folder_id )
118
+
119
+ def create_folder (self , folder_name , parent_folder_id ):
120
+ """
121
+ Creates a new folder in the specified parent folder.
122
+ :param folder_name: The name of the new folder to create.
123
+ :param parent_folder_id: The ID of the parent folder where the new folder will be created.
124
+ :return: The ID of the newly created folder if successful, otherwise None.
125
+ """
126
+ try :
127
+ folder_metadata = {
128
+ 'name' : folder_name ,
129
+ 'mimeType' : 'application/vnd.google-apps.folder' ,
130
+ 'parents' : [parent_folder_id ]
131
+ }
132
+ folder = self .service .files ().create (
133
+ body = folder_metadata ,
134
+ fields = 'id' ,
135
+ supportsAllDrives = True
136
+ ).execute ()
137
+ return folder .get ('id' )
138
+ except Exception as e :
139
+ logger .error (f"Error creating folder '{ folder_name } ': { e } " )
140
+ return None
141
+
142
+ def _find_folder_id (self , parent_folder_id , folder_name ):
143
+ """
144
+ Helper method to find the folder ID by name within a specified parent folder.
145
+ :param parent_folder_id: The ID of the parent folder to search within.
146
+ :param folder_name: The name of the folder to find.
147
+ :return: The ID of the folder if found, otherwise None.
148
+ """
149
+ page_token = None
150
+ while True :
151
+ results = self .service .files ().list (
152
+ q = f"'{ parent_folder_id } ' in parents and trashed = false and mimeType = 'application/vnd.google-apps.folder'" ,
153
+ includeItemsFromAllDrives = True ,
154
+ supportsAllDrives = True ,
155
+ pageSize = 100 ,
156
+ fields = "nextPageToken, files(id, name)" ,
157
+ pageToken = page_token
158
+ ).execute ()
159
+
160
+ items = results .get ('files' , [])
161
+ for item in items :
162
+ if item ['name' ] == folder_name :
163
+ return item ['id' ]
164
+
165
+ page_token = results .get ('nextPageToken' , None )
166
+ if page_token is None :
167
+ break
168
+
169
+ return None
170
+
171
+ def get_folder_id_by_path (self , folder_path , parent_folder_id ):
172
+ """
173
+ Checks if a folder path exists in Google Drive and returns the ID of the last folder in the path.
174
+ :param folder_path: The path of folders to check, e.g., '2024/09/15'.
175
+ :param parent_folder_id: The ID of the parent folder where the search starts.
176
+ :return: The ID of the last folder in the path if it exists, otherwise None.
177
+ """
178
+ folder_names = folder_path .split ('/' )
179
+ current_parent_id = parent_folder_id
180
+
181
+ for folder_name in folder_names :
182
+ folder_id = self ._find_folder_id (current_parent_id , folder_name )
183
+ if folder_id :
184
+ current_parent_id = folder_id
185
+ else :
186
+ # Folder in path does not exist
187
+ return None
188
+
189
+ return current_parent_id
190
+
191
+ def is_folder_name_exists (self , folder_id , target_folder_name ):
192
+ """
193
+ Recursively checks if a folder with the specified name exists in a given Google Drive folder.
194
+ :param folder_id: The ID of the folder to start the search.
195
+ :param target_folder_name: The name of the folder to find.
196
+ :return: True if the folder exists, otherwise False.
197
+ """
198
+ page_token = None
199
+ while True :
200
+ results = self .service .files ().list (
201
+ q = f"'{ folder_id } ' in parents and trashed = false" ,
202
+ includeItemsFromAllDrives = True ,
203
+ supportsAllDrives = True ,
204
+ pageSize = 100 ,
205
+ fields = "nextPageToken, files(id, name, mimeType)" ,
206
+ pageToken = page_token
207
+ ).execute ()
208
+
209
+ items = results .get ('files' , [])
210
+ for item in items :
211
+ if item ['mimeType' ] == 'application/vnd.google-apps.folder' :
212
+ if item ['name' ] == target_folder_name :
213
+ return item ['id' ]
214
+ # Recursively check in the subfolders
215
+ if self .is_folder_name_exists (item ['id' ], target_folder_name ):
216
+ return item ['id' ]
217
+
218
+ page_token = results .get ('nextPageToken' , None )
219
+ if page_token is None :
220
+ break
221
+
222
+ return False
223
+
224
+ def get_drive_folder_url (self , folder_path , parent_folder_id ):
225
+ """
226
+ Retrieves the Google Drive folder URL based on the folder path without uploading a file.
227
+ :param folder_path: The folder path to retrieve or create.
228
+ :param parent_folder_id: The parent folder ID where the folder path begins.
229
+ :return: The Google Drive URL for the folder.
230
+ """
231
+ # Check if the folder exists in the specified path
232
+ folder_id = self .get_folder_id_by_path (folder_path , parent_folder_id )
233
+
234
+ if folder_id :
235
+ # Folder exists, return the folder URL
236
+ folder_url = f"{ self ._google_drive_path } /{ folder_id } "
237
+ return folder_url
238
+ else :
239
+ # If folder does not exist, create it along the path
240
+ folder_id = self .create_folder_at_path (folder_path , parent_folder_id )
241
+ if folder_id :
242
+ # Return the newly created folder's URL
243
+ folder_url = f"{ self ._google_drive_path } /{ folder_id } "
244
+ return folder_url
245
+ else :
246
+ logger .error (f"Unable to create or find the folder path: { folder_path } " )
247
+ return None
248
+
51
249
def list_files_in_folder (self , folder_id , level = 0 ):
52
250
"""
53
251
Recursively lists files and folders in a specified Google Drive folder.
@@ -82,18 +280,6 @@ def list_files_in_folder(self, folder_id, level=0):
82
280
83
281
return items
84
282
85
- def list_folders_and_files_in_shared_drive (self , shared_drive_id ):
86
- """
87
- Lists all folders and files within a specified Google Drive shared drive.
88
- :param shared_drive_id: The ID of the shared drive to list.
89
- """
90
- try :
91
- logger .info (f'Folders and Files in shared drive with ID { shared_drive_id } :' )
92
- self .list_files_in_folder (shared_drive_id )
93
- logger .info ("Folder and file listing successful!" )
94
- except Exception as e :
95
- logger .error (f"An error occurred: { e } " )
96
-
97
283
def find_existing_file (self , file_name , shared_drive_id ):
98
284
"""
99
285
Finds an existing file in a specified Google Drive shared drive.
@@ -113,9 +299,10 @@ def find_existing_file(self, file_name, shared_drive_id):
113
299
114
300
def upload_file (self , file_path , shared_drive_id ):
115
301
"""
116
- Uploads a file to a specified Google Drive shared drive and override if exist
302
+ Uploads a file to a specified Google Drive shared drive and returns the Google Drive folder URL.
117
303
:param file_path: The local path of the file to upload.
118
304
:param shared_drive_id: The ID of the shared drive to upload the file to.
305
+ :return: The Google Drive URL for the folder containing the uploaded file.
119
306
"""
120
307
try :
121
308
file_name = os .path .basename (file_path )
@@ -124,7 +311,7 @@ def upload_file(self, file_path, shared_drive_id):
124
311
125
312
if existing_file :
126
313
file_id = existing_file ['id' ]
127
- file = self .service .files ().update (
314
+ self .service .files ().update (
128
315
fileId = file_id ,
129
316
media_body = media ,
130
317
supportsAllDrives = True ,
@@ -142,7 +329,31 @@ def upload_file(self, file_path, shared_drive_id):
142
329
supportsAllDrives = True ,
143
330
fields = 'id'
144
331
).execute ()
332
+ file_id = file .get ('id' )
145
333
logger .info (f"File '{ file_name } ' uploaded successfully to shared drive with ID: { shared_drive_id } " )
146
334
335
+ # Return the Google Drive URL for the folder containing the uploaded file
336
+ folder_url = f"{ self ._google_drive_path } /{ shared_drive_id } "
337
+ return folder_url
338
+
147
339
except Exception as e :
148
340
logger .error (f"An error occurred: { e } " )
341
+ return None
342
+
343
+ def upload_file_to_folder (self , file_path : str , folder_path : str , parent_folder_id : str ):
344
+ """
345
+ This method uploads file into google drive folder_path and return the folder url
346
+ @param file_path: the file path to upload to google drive
347
+ @param folder_path: google drive folder path
348
+ @param parent_folder_id: the base drive folder
349
+ @return:the folder url
350
+ """
351
+ folder_id = self .get_folder_id_by_path (
352
+ folder_path = folder_path ,
353
+ parent_folder_id = parent_folder_id )
354
+ # new path
355
+ if not folder_id :
356
+ folder_id = self .create_folder_at_path (
357
+ folder_path = folder_path ,
358
+ parent_folder_id = parent_folder_id )
359
+ return self .upload_file (file_path = file_path , shared_drive_id = folder_id )
0 commit comments