From 738e8bad1fff51e8587a04eaafe66cbf74447cd3 Mon Sep 17 00:00:00 2001 From: Sepehr0Day <61628516+Sepehr0Day@users.noreply.github.com> Date: Tue, 5 Mar 2024 02:23:37 +0330 Subject: [PATCH] Add files via upload --- DriveX/__init__.py | 2 + DriveX/core/Authenticator/Authenticator.py | 48 +++++++++ DriveX/core/Authenticator/__init__.py | 1 + DriveX/core/FileManager/FileManager.py | 108 +++++++++++++++++++ DriveX/core/FileManager/__init__.py | 1 + DriveX/core/FolderManager/FolderManager.py | 89 +++++++++++++++ DriveX/core/FolderManager/__init__.py | 1 + DriveX/core/GoogleDriveAPI/GoogleDriveAPI.py | 95 ++++++++++++++++ DriveX/core/GoogleDriveAPI/__init__.py | 0 DriveX/core/ServiceBuilder/ServiceBuilder.py | 33 ++++++ DriveX/core/ServiceBuilder/__init__.py | 1 + DriveX/core/__init__.py | 5 + DriveX/error/ERR.py | 55 ++++++++++ DriveX/error/__init__.py | 2 + DriveX/error/error.py | 74 +++++++++++++ LICENSE | 21 ++++ README.md | 74 +++++++++++++ requirements.txt | 2 + setup.py | 18 ++++ 19 files changed, 630 insertions(+) create mode 100644 DriveX/__init__.py create mode 100644 DriveX/core/Authenticator/Authenticator.py create mode 100644 DriveX/core/Authenticator/__init__.py create mode 100644 DriveX/core/FileManager/FileManager.py create mode 100644 DriveX/core/FileManager/__init__.py create mode 100644 DriveX/core/FolderManager/FolderManager.py create mode 100644 DriveX/core/FolderManager/__init__.py create mode 100644 DriveX/core/GoogleDriveAPI/GoogleDriveAPI.py create mode 100644 DriveX/core/GoogleDriveAPI/__init__.py create mode 100644 DriveX/core/ServiceBuilder/ServiceBuilder.py create mode 100644 DriveX/core/ServiceBuilder/__init__.py create mode 100644 DriveX/core/__init__.py create mode 100644 DriveX/error/ERR.py create mode 100644 DriveX/error/__init__.py create mode 100644 DriveX/error/error.py create mode 100644 LICENSE create mode 100644 README.md create mode 100644 requirements.txt create mode 100644 setup.py diff --git a/DriveX/__init__.py b/DriveX/__init__.py new file mode 100644 index 0000000..a6828bd --- /dev/null +++ b/DriveX/__init__.py @@ -0,0 +1,2 @@ +from DriveX.core import * +from DriveX.error.error import * diff --git a/DriveX/core/Authenticator/Authenticator.py b/DriveX/core/Authenticator/Authenticator.py new file mode 100644 index 0000000..09bb902 --- /dev/null +++ b/DriveX/core/Authenticator/Authenticator.py @@ -0,0 +1,48 @@ +from google.auth.transport.requests import Request +from google_auth_oauthlib.flow import InstalledAppFlow +import os, pickle, json + +class Authenticator: + """Responsible for handling authentication with Google Drive.""" + + def __init__(self, credentials_path, token_path, scopes): + """Initialize the Authenticator. + + Args: + credentials_path (str): Path to the credentials file. + token_path (str): Path to the token file. + scopes (str or list of str): The scope or scopes to be requested during the authorization flow. + """ + self.credentials_path = credentials_path + self.token_path = token_path + self.scopes = scopes + + def get_credentials(self): + """Retrieve or generate OAuth2 credentials for accessing Google Drive. + + Returns: + google.oauth2.credentials.Credentials: OAuth2 credentials. + """ + creds = None + if os.path.exists(self.token_path): + with open(self.token_path, 'rb') as token: + creds = pickle.load(token) + if not creds or not creds.valid: + if creds and creds.expired and creds.refresh_token: + creds.refresh(Request()) + else: + flow = InstalledAppFlow.from_client_config( + self._read_credentials_file(), self.scopes) + creds = flow.run_local_server(port=0) + with open(self.token_path, 'wb') as token: + pickle.dump(creds, token) + return creds + + def _read_credentials_file(self): + """Read the credentials file and return its content. + + Returns: + dict: Content of the credentials file. + """ + with open(self.credentials_path) as f: + return json.load(f) diff --git a/DriveX/core/Authenticator/__init__.py b/DriveX/core/Authenticator/__init__.py new file mode 100644 index 0000000..3fd0f3d --- /dev/null +++ b/DriveX/core/Authenticator/__init__.py @@ -0,0 +1 @@ +from .Authenticator import Authenticator \ No newline at end of file diff --git a/DriveX/core/FileManager/FileManager.py b/DriveX/core/FileManager/FileManager.py new file mode 100644 index 0000000..380dd43 --- /dev/null +++ b/DriveX/core/FileManager/FileManager.py @@ -0,0 +1,108 @@ +import os, io +from ...error import * +from googleapiclient.http import MediaFileUpload, MediaIoBaseDownload + + +class FileManager: + """Handles file-related operations in Google Drive.""" + + def __init__(self, service): + self.service = service + + def upload_file(self, file_path, folder_id=None): + """ + Upload a file to Google Drive. + + Args: + file_path (str): Path to the file to upload. + folder_id (str, optional): ID of the folder to upload the file to. Defaults to None. + + Returns: + str: ID of the uploaded file. + """ + try: + file_metadata = {'name': os.path.basename(file_path)} + if folder_id: + file_metadata['parents'] = [folder_id] + media = MediaFileUpload(file_path, resumable=True) + file = self.service.files().create(body=file_metadata, media_body=media, fields='id').execute() + return file.get('id') + except Exception as e: + raise FileError(f"Failed to upload file: {str(e)}") + + def download_file(self, file_id, destination_path): + """ + Download a file from Google Drive. + + Args: + file_id (str): ID of the file to download. + destination_path (str): Path to save the downloaded file. + + Returns: + None + """ + try: + request = self.service.files().get_media(fileId=file_id) + with open(destination_path, 'wb') as fh: + downloader = MediaIoBaseDownload(fh, request) + done = False + while not done: + status, done = downloader.next_chunk() + except Exception as e: + raise FileError(f"Failed to download file: {str(e)}") + + def read_file(self, file_id): + """ + Read the contents of a file from Google Drive. + + Args: + file_id (str): ID of the file to read. + + Returns: + str: Contents of the file. + """ + try: + request = self.service.files().get_media(fileId=file_id) + downloaded_file = io.BytesIO() + downloader = MediaIoBaseDownload(downloaded_file, request) + done = False + while not done: + status, done = downloader.next_chunk() + return downloaded_file.getvalue().decode('utf-8') + except Exception as e: + raise FileError(f"Failed to read file: {str(e)}") + + def delete_file(self, file_id): + """ + Delete a file from Google Drive. + + Args: + file_id (str): ID of the file to delete. + + Returns: + None + """ + try: + self.service.files().delete(fileId=file_id).execute() + except Exception as e: + raise FileError(f"Failed to delete file: {str(e)}") + + def copy_file(self, file_id, new_parent_id=None): + """ + Copy a file in Google Drive. + + Args: + file_id (str): ID of the file to copy. + new_parent_id (str, optional): ID of the new parent folder. Defaults to None. + + Returns: + str: ID of the newly copied file. + """ + try: + file = {'parents': []} + if new_parent_id: + file['parents'].append(new_parent_id) + copied_file = self.service.files().copy(fileId=file_id, body=file).execute() + return copied_file.get('id') + except Exception as e: + raise FolderError(f"Failed to copy file: {str(e)}") \ No newline at end of file diff --git a/DriveX/core/FileManager/__init__.py b/DriveX/core/FileManager/__init__.py new file mode 100644 index 0000000..be87b45 --- /dev/null +++ b/DriveX/core/FileManager/__init__.py @@ -0,0 +1 @@ +from .FileManager import FileManager \ No newline at end of file diff --git a/DriveX/core/FolderManager/FolderManager.py b/DriveX/core/FolderManager/FolderManager.py new file mode 100644 index 0000000..de886a7 --- /dev/null +++ b/DriveX/core/FolderManager/FolderManager.py @@ -0,0 +1,89 @@ +from ...error.error import * + +class FolderManager: + """Handles folder-related operations in Google Drive.""" + + def __init__(self, service): + self.service = service + + def create_folder(self, folder_name, parent_folder_id=None): + """ + Create a new folder in Google Drive. + + Args: + folder_name (str): Name of the new folder. + parent_folder_id (str, optional): ID of the parent folder. Defaults to None. + + Returns: + str: ID of the newly created folder. + """ + try: + folder_metadata = { + 'name': folder_name, + 'mimeType': 'application/vnd.google-apps.folder' + } + if parent_folder_id: + folder_metadata['parents'] = [parent_folder_id] + folder = self.service.files().create(body=folder_metadata, fields='id').execute() + return folder.get('id') + except Exception as e: + raise FolderError(f"Failed to create folder: {str(e)}") + + def move_file(self, file_id, new_parent_id): + """ + Move a file to a different folder. + + Args: + file_id (str): ID of the file to move. + new_parent_id (str): ID of the new parent folder. + + Returns: + dict: Updated file metadata. + """ + try: + file = self.service.files().get(fileId=file_id, fields='parents').execute() + previous_parents = ",".join(file.get('parents')) + file = self.service.files().update( + fileId=file_id, + addParents=new_parent_id, + removeParents=previous_parents, + fields='id, parents' + ).execute() + return file + except Exception as e: + raise FolderError(f"Failed to move file: {str(e)}") + + def copy_folder(self, folder_id, new_parent_id=None): + """ + Copy a folder in Google Drive. + + Args: + folder_id (str): ID of the folder to copy. + new_parent_id (str, optional): ID of the new parent folder. Defaults to None. + + Returns: + str: ID of the newly copied folder. + """ + try: + folder = {'parents': []} + if new_parent_id: + folder['parents'].append(new_parent_id) + copied_folder = self.service.files().copy(fileId=folder_id, body=folder).execute() + return copied_folder.get('id') + except Exception as e: + raise FolderError(f"Failed to copy folder: {str(e)}") + + def delete_folder(self, folder_id): + """ + Delete a folder from Google Drive. + + Args: + folder_id (str): ID of the folder to delete. + + Returns: + None + """ + try: + self.service.files().delete(fileId=folder_id).execute() + except Exception as e: + raise FolderError(f"Failed to delete folder: {str(e)}") \ No newline at end of file diff --git a/DriveX/core/FolderManager/__init__.py b/DriveX/core/FolderManager/__init__.py new file mode 100644 index 0000000..c8e70fc --- /dev/null +++ b/DriveX/core/FolderManager/__init__.py @@ -0,0 +1 @@ +from .FolderManager import FolderManager \ No newline at end of file diff --git a/DriveX/core/GoogleDriveAPI/GoogleDriveAPI.py b/DriveX/core/GoogleDriveAPI/GoogleDriveAPI.py new file mode 100644 index 0000000..c521fa3 --- /dev/null +++ b/DriveX/core/GoogleDriveAPI/GoogleDriveAPI.py @@ -0,0 +1,95 @@ +from ...error.error import * +from datetime import datetime + +class GoogleDriveAPI: + """Provides an interface for interacting with Google Drive.""" + + def __init__(self, credentials, service): + self.credentials = credentials + self.service = service + + def list_files(self, page_size=10, query=None): + """ + List files in Google Drive. + + Args: + page_size (int, optional): Number of files to retrieve per page. Defaults to 10. + query (str, optional): Query string to filter the files. Defaults to None. + + Returns: + list: List of files matching the query. + """ + try: + query_params = {'pageSize': page_size, 'fields': 'nextPageToken, files(id, name, createdTime, permissions)'} + if query: + query_params['q'] = query + results = self.service.files().list(**query_params).execute() + items = results.get('files', []) + + for item in items: + permissions = item.get('permissions', []) + if isinstance(permissions, str): + permissions = eval(permissions) + item['permissions'] = self.format_permissions(permissions) + + return items + except Exception as e: + raise FolderError(f"Failed to list files: {str(e)}") + + def _convert_size(self, size_bytes): + """ + Convert file size in bytes to human-readable format. + + Args: + size_bytes (int or str): File size in bytes. + + Returns: + str: Human-readable file size. + """ + try: + size_bytes = int(size_bytes) + except ValueError: + return size_bytes + + sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'] + i = 0 + while size_bytes >= 1024 and i < len(sizes) - 1: + size_bytes /= 1024.0 + i += 1 + return "{:.2f} {}".format(size_bytes, sizes[i]) + + def get_file_info(self, file_id): + """ + Get detailed information about a file. + + Args: + file_id (str): ID of the file. + + Returns: + dict: File metadata. + """ + try: + file = self.service.files().get(fileId=file_id).execute() + return file + except Exception as e: + raise FileError(f"Failed to get file info: {str(e)}") + + @staticmethod + def format_permissions(permissions): + """ + Format permissions for display. + + Args: + permissions (list): List of permission dictionaries. + + Returns: + str: Formatted permissions string. + """ + if not permissions: + return "No permissions" + permissions_str = "" + for perm in permissions: + role = perm.get("role", "Unknown role") + email = perm.get("emailAddress", "Unknown email") + permissions_str += f"Role: {role}, Email: {email}\n" + return permissions_str diff --git a/DriveX/core/GoogleDriveAPI/__init__.py b/DriveX/core/GoogleDriveAPI/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/DriveX/core/ServiceBuilder/ServiceBuilder.py b/DriveX/core/ServiceBuilder/ServiceBuilder.py new file mode 100644 index 0000000..a17d382 --- /dev/null +++ b/DriveX/core/ServiceBuilder/ServiceBuilder.py @@ -0,0 +1,33 @@ +from ...error.error import * +from ..Authenticator.Authenticator import Authenticator +from googleapiclient.discovery import build + +class ServiceBuilder: + """Builds the Google Drive service.""" + + def __init__(self, credentials_path, token_path, token_name ,scopes="https://www.googleapis.com/auth/drive"): + """Initialize the ServiceBuilder. + + Args: + credentials_path (str): Path to the credentials file. + token_path (str): Path to the token file. + scopes (str or list of str): The scope or scopes to be requested during the authorization flow. + """ + self.credentials_path = credentials_path + self.token_path = token_path + self.token_name = token_name + self.scopes = scopes + + def build_service(self): + """Builds and returns the Google Drive service. + + Returns: + googleapiclient.discovery.Resource: Google Drive service object. + """ + try: + authenticator = Authenticator(self.credentials_path, f"{self.token_path}/{self.token_name}.pickle" , self.scopes) + credentials = authenticator.get_credentials() + service = build('drive', 'v3', credentials=credentials) + return service + except GoogleDriveError as e: + raise GoogleDriveError(f"Failed to build service: {str(e)}") diff --git a/DriveX/core/ServiceBuilder/__init__.py b/DriveX/core/ServiceBuilder/__init__.py new file mode 100644 index 0000000..467df22 --- /dev/null +++ b/DriveX/core/ServiceBuilder/__init__.py @@ -0,0 +1 @@ +from .ServiceBuilder import ServiceBuilder \ No newline at end of file diff --git a/DriveX/core/__init__.py b/DriveX/core/__init__.py new file mode 100644 index 0000000..680882c --- /dev/null +++ b/DriveX/core/__init__.py @@ -0,0 +1,5 @@ +from .Authenticator.Authenticator import Authenticator +from .GoogleDriveAPI.GoogleDriveAPI import GoogleDriveAPI +from .FolderManager.FolderManager import FolderManager +from .FileManager.FileManager import FileManager +from .ServiceBuilder.ServiceBuilder import ServiceBuilder \ No newline at end of file diff --git a/DriveX/error/ERR.py b/DriveX/error/ERR.py new file mode 100644 index 0000000..a0c7740 --- /dev/null +++ b/DriveX/error/ERR.py @@ -0,0 +1,55 @@ +class GoogleDriveError(Exception): + """Base class for Google Drive API errors.""" + pass + +class AuthenticationError(GoogleDriveError): + """Error raised for authentication-related issues.""" + pass + +class FileError(GoogleDriveError): + """Error raised for file-related issues.""" + pass + +class FolderError(GoogleDriveError): + """Error raised for folder-related issues.""" + pass + +class RateLimitError(GoogleDriveError): + """Error raised when the API rate limit is exceeded.""" + pass + +class NetworkError(GoogleDriveError): + """Error raised for network-related issues.""" + pass + +class PermissionError(GoogleDriveError): + """Error raised when permissions are insufficient.""" + pass + +class QuotaError(GoogleDriveError): + """Error raised when storage quota is exceeded.""" + pass + +class NotFoundError(GoogleDriveError): + """Error raised when a resource is not found.""" + pass + +class ConflictError(GoogleDriveError): + """Error raised when there is a conflict with existing resources.""" + pass + +class InvalidRequestError(GoogleDriveError): + """Error raised when an invalid request is made.""" + pass + +class TooManyRequestsError(GoogleDriveError): + """Error raised when the API rate limit is exceeded.""" + pass + +class ServerError(GoogleDriveError): + """Error raised for server-related issues.""" + pass + +class ClientError(GoogleDriveError): + """Error raised for client-related issues.""" + pass diff --git a/DriveX/error/__init__.py b/DriveX/error/__init__.py new file mode 100644 index 0000000..ee89c95 --- /dev/null +++ b/DriveX/error/__init__.py @@ -0,0 +1,2 @@ +from .error import * +from .error import * \ No newline at end of file diff --git a/DriveX/error/error.py b/DriveX/error/error.py new file mode 100644 index 0000000..3f963b4 --- /dev/null +++ b/DriveX/error/error.py @@ -0,0 +1,74 @@ +from ..error.ERR import * + +class GoogleDriveErrorHandler: + """Handles errors that may occur during interactions with Google Drive.""" + + def __init__(self): + """Initialize the GoogleDriveErrorHandler.""" + pass + + def handle_error(self, exception): + """Handles the given exception and provides an explanation to the user.""" + try: + if isinstance(exception, AuthenticationError): + print("Authentication error occurred:", exception) + print("Please check your credentials and try again.") + elif isinstance(exception, FileError): + print("File-related error occurred:", exception) + print("Please check the file and try again.") + elif isinstance(exception, FolderError): + print("Folder-related error occurred:", exception) + print("Please check the folder and try again.") + elif isinstance(exception, RateLimitError): + print("Rate limit exceeded error occurred:", exception) + print("Please wait and try again later.") + elif isinstance(exception, NetworkError): + print("Network error occurred:", exception) + print("Please check your internet connection and try again.") + elif isinstance(exception, PermissionError): + print("Permission error occurred:", exception) + print("You do not have sufficient permissions to perform this action.") + elif isinstance(exception, QuotaError): + print("Quota error occurred:", exception) + print("Your storage quota is exceeded. Please free up space or upgrade your plan.") + elif isinstance(exception, NotFoundError): + print("Resource not found error occurred:", exception) + print("The requested resource was not found.") + elif isinstance(exception, ConflictError): + print("Conflict error occurred:", exception) + print("There was a conflict with existing resources.") + elif isinstance(exception, InvalidRequestError): + print("Invalid request error occurred:", exception) + print("The request made is invalid.") + elif isinstance(exception, ValueError): + print("Value error occurred:", exception) + print("Please provide valid input.") + elif isinstance(exception, TypeError): + print("Type error occurred:", exception) + print("Please check the data types being used.") + elif isinstance(exception, IndexError): + print("Index error occurred:", exception) + print("Please check the index value being used.") + elif isinstance(exception, KeyError): + print("Key error occurred:", exception) + print("Please check if the key exists in the dictionary.") + elif isinstance(exception, NotImplementedError): + print("Not implemented error occurred:", exception) + print("This feature is not implemented yet.") + elif isinstance(exception, TimeoutError): + print("Timeout error occurred:", exception) + print("The request timed out. Please try again later.") + elif isinstance(exception, TooManyRequestsError): + print("Too many requests error occurred:", exception) + print("You have exceeded the maximum number of requests. Please try again later.") + elif isinstance(exception, ServerError): + print("Server error occurred:", exception) + print("There was an internal server error. Please try again later.") + elif isinstance(exception, ClientError): + print("Client error occurred:", exception) + print("There was an error on the client side. Please try again.") + else: + print("An unknown error occurred:", exception) + print("Please try again later or contact support.") + except Exception as e: + print("Error handling error:", e) \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..23059ba --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Sepehr0Day + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8ab3b02 --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +# DriveX + +## Connect to Google Drive + +1. **Installation:** + + Install the DriveX library from PyPI or GitHub. + + ```bash + pip install DriveX + ``` + + or + + ```bash + git clone https://github.com/Sepehr0Day/DriveX.git + ``` + +2. **Obtain Google Drive API Credentials:** + + - Create a project in the Google Cloud Console. + - Enable the Google Drive API for your project. + - Create OAuth client ID credentials. + - Download the credentials JSON file. + +3. **Code:** + + ```python + from DriveX import ServiceBuilder, GoogleDriveAPI + + # Path to the credentials JSON file + credentials_path = 'credentials.json' + + # Path to save token + token_path = 'Resources/Authenticator' + + # Name for the token file + token_name = 'token' + + # Create ServiceBuilder object + service_builder = ServiceBuilder(credentials_path=credentials_path, token_path=token_path, token_name=token_name) + + try: + # Build service + drive_service = service_builder.build_service() + + # Connect to Google Drive API + drive_api = GoogleDriveAPI(service=drive_service, credentials=credentials_path) + + print("You Successfully Connected To Your Google Drive Account!") + + except Exception as e: + print(f"An error occurred: {e}") + ``` + + Make sure to replace `'credentials.json'` with the path to your credentials JSON file obtained from the Google Cloud Console. + +## Documentation DriveX: + + For more information and advanced usage, refer to the [DriveX documentation](https://Sepehr0day.github.io/DriveX.html). + + +## License + +This project is licensed under the MIT License. See the [LICENSE](https://github.com/Sepehr0Day/DriveX/blob/main/LICENSE) file for details. + + + +## Developer +- **Telegram**: [t.me/Sepehr0Day](https://t.me/Sepehr0Day) + +--- + +Feel free to contribute to the project or report any issues on the GitHub repository! \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..aba23fc --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +google-auth-oauthlib +google-auth \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..dd19d47 --- /dev/null +++ b/setup.py @@ -0,0 +1,18 @@ +from setuptools import setup, find_packages + +setup( + name='DriveX', + version='0.1', + packages=find_packages(), + license='MIT', + description='DriveX library to connect and work with Google Drive!', + long_description=open('README.md').read(), + long_description_content_type='text/markdown', + author='Sepehr', + author_email='sphrz2324@gmail.com', + url='https://github.com/Sepehr0Day/DriveX/', + install_requires=[ + 'google-auth-oauthlib', + 'google-auth' + ], +)