Commits

Anonymous committed 46c0b8e

google drive backend

Comments (0)

Files changed (1)

storages/backends/google_drive.py

+# Django storage using google drive
+# Hiram <fawf3f2w@hmvp.nl>
+#
+import os
+import httplib2
+from dateutil import parser
+
+
+from django.conf import settings
+from django.core.files.storage import Storage
+from django.core.files.base import File
+from django.core.exceptions import ImproperlyConfigured
+
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+
+
+try:
+    from apiclient.discovery import build
+    from apiclient.http import MediaFileUpload
+    from apiclient import errors
+    from oauth2client import client
+except ImportError:
+    raise ImproperlyConfigured("Could not load google drive sdk")
+
+
+class GoogleDriveStorage(Storage):
+    """
+    Uses google drive as backend
+
+    To use this do the following:
+    create an oauth2 client id for an installed app using: https://code.google.com/apis
+    dowload the client_secrets.json set the following values in settings:
+
+    GDRIVE_CLIENTSECRETS_LOCATION = <location of client_secrets.json>
+    GDRIVE_CREDENTIALS_FILE = <location of credentials cache>
+    GDRIVE_ROOT_FOLDER_NAME = <the name of the folder in which to place the files>
+
+    It is best to create a new account for this and not use an existing one
+
+    This is untested code!
+
+    """
+    files = []
+    folders = []
+    root = None
+
+    """Django storage derived class using apache libcloud to operate
+    on supported providers"""
+    def __init__(self, provider_name=None, option=None):
+        if os.path.exists(settings.GDRIVE_CREDENTIALS_FILE):
+            #load credentials
+            with open(settings.GDRIVE_CREDENTIALS_FILE, 'r') as cred_file:
+                credentials = client.Credentials.new_from_json(cred_file.read())
+        else:
+            # Check https://developers.google.com/drive/scopes for all available scopes
+            OAUTH_SCOPE = 'https://www.googleapis.com/auth/drive'
+
+            # Run through the OAuth flow and retrieve credentials
+            flow = client.flow_from_clientsecrets(settings.GDRIVE_CLIENTSECRETS_LOCATION, OAUTH_SCOPE)
+            authorize_url = flow.step1_get_authorize_url('oob')
+            print 'Go to the following link in your browser: ' + authorize_url
+            code = raw_input('Enter verification code: ').strip()
+            credentials = flow.step2_exchange(code)
+
+            with open(settings.GDRIVE_CREDENTIALS_FILE, 'w+') as cred_file:
+                cred_file.write(credentials.to_json())
+
+        # Create an httplib2.Http object and authorize it with our credentials
+        http = httplib2.Http()
+        http = credentials.authorize(http)
+
+        self.service = build('drive', 'v2', http=http)
+        self.files = self._retrieve_all_files()
+        self.folders = self._get_children(self.root['id'])
+
+    def delete(self, name):
+        """Delete object on remote"""
+        file_id = self._get_file_id(name)
+
+        if file_id:
+            try:
+                self.service.files().delete(fileId=file_id).execute()
+                del self.files[file_id]
+            except errors.HttpError, error:
+                raise Exception('An error occurred: %s' % error)
+        else:
+            raise Exception('Object to delete does not exists')
+
+    def exists(self, name):
+        file_id = self._get_file_id(name)
+        return file_id in self.files
+
+    def _get_file_id(self, name):
+        names = name.split('/')
+
+        self.root
+        current_folder = self.folders
+        for name in names[:-1]:
+            if name in current_folder:
+                current_folder = current_folder[name]['children']
+            else:
+                return None
+
+        filename = names[-1]
+        if filename in current_folder:
+            return current_folder[filename]['id']
+        else:
+            return None
+
+    def _retrieve_all_files(self):
+        """Retrieve a list of File resources.
+
+        Args:
+            service: Drive API service instance.
+        Returns:
+            List of File resources.
+        """
+        objects = []
+        page_token = None
+        while True:
+            try:
+                param = {}
+                if page_token:
+                    param['pageToken'] = page_token
+                files = self.service.files().list(**param).execute()
+
+                objects.extend(files['items'])
+                page_token = files.get('nextPageToken')
+                if not page_token:
+                    break
+            except errors.HttpError, error:
+                raise Exception('An error occurred: %s' % error)
+
+        result = {}
+        for object_data in objects:
+
+            #set root filedata
+            if object_data['title'] == settings.GDRIVE_ROOT_FOLDER_NAME:
+                self.root = object_data
+
+            result[object_data['id']] = object_data
+
+        if self.root is None:
+            raise Exception('GDRIVE_ROOT_FOLDER_NAME not found')
+
+        return result
+
+    def _get_children(self, object_id):
+        response = self.service.children().list(folderId=object_id, **{}).execute()
+
+        children = response.get('items', [])
+
+        result = {}
+        for child in children:
+            name = self.files[child['id']]['title']
+            is_folder = self.files[child['id']]['mimeType'] == 'application/vnd.google-apps.folder'
+            if is_folder:
+                result[name] = {'id': child['id'], 'children': self._get_children(child['id'])}
+            else:
+                result[name] = {'id': child['id']}
+
+        return result
+
+    def listdir(self, path='/'):
+        """Lists the contents of the specified path,
+        returning a 2-tuple of lists; the first item being
+        directories, the second item being files.
+        """
+        names = path.split('/')
+
+        self.root
+        current_folder = self.folders
+        for name in names:
+            if name in current_folder:
+                current_folder = current_folder[name]['children']
+            else:
+                return None
+
+        files = []
+        dirs = []
+        for objekt in current_folder:
+            if 'children' in objekt:
+                dirs.append(path+'/'+self.files[objekt['id']])
+            else:
+                files.append(path+'/'+self.files[objekt['id']])
+
+        return (dirs, files)
+
+    def modified_time(self, name):
+        ''' not completely accurate since we cannot set the modified time when uploading'''
+        file_id = self._get_file_id(name)
+        if file_id:
+            return parser.parse(self.files[file_id]['modifiedDate']).replace(tzinfo=None)
+        else:
+            return None
+
+    def size(self, name):
+        file_id = self._get_file_id(name)
+        if file_id:
+            return self.files[file_id]['fileSize']
+        else:
+            return -1
+
+    def url(self, name):
+        file_id = self._get_file_id(name)
+        if file_id:
+            return self.files[file_id]['webViewLink']
+        else:
+            return ''
+
+    def _open(self, name, mode='rb'):
+        file_id = self._get_file_id(name)
+        return GoogleDriveFile(file_id, self, mode)
+
+    def _read(self, name, start_range=None, end_range=None):
+        file_id = self._get_file_id(name)
+        download_url = self.files[file_id].get('downloadUrl')
+
+        if download_url:
+            resp, content = self.service._http.request(download_url)
+            if resp.status == 200:
+                return content
+            else:
+                raise Exception('An error occurred: %s' % resp)
+        else:
+            # The file doesn't have any content stored on Drive.
+            return None
+
+    def _save(self, name, filename):
+        if self.exists(name):
+            file_id = self._get_file_id(name)
+            file_data = self.files[file_id]
+            try:
+                # File's new content.
+                media_body = MediaFileUpload(
+                    str(filename), resumable=True
+                )
+
+                # Send the request to the API.
+                response = self.service.files().update(
+                    fileId=file_id,
+                    body=file_data,
+                    newRevision=False,
+                    media_body=media_body
+                ).execute()
+
+                self.files[file_id] = response
+            except errors.HttpError, error:
+                raise Exception('An error occured: %s' % error)
+
+        else:
+            name_components = name.rpartition('/')
+            title = name_components[2]
+            parent_id = self._get_file_id(name_components[0])
+            media_body = MediaFileUpload(str(filename), resumable=True)
+            body = {
+                'title': title,
+            }
+            # Set the parent folder.
+            if parent_id:
+                body['parents'] = [{'id': parent_id}]
+
+            try:
+                response = self.service.files().insert(
+                    body=body,
+                    media_body=media_body,
+                ).execute()
+                self.files[response['id']] = response
+            except errors.HttpError, error:
+                raise Exception('An error occured: %s' % error)
+
+        return name
+
+
+class GoogleDriveFile(File):
+    """File inherited class for libcloud storage objects read and write"""
+    def __init__(self, name, storage, mode):
+        self._name = name
+        self._storage = storage
+        self._mode = mode
+        self._is_dirty = False
+        self.file = StringIO()
+        self.start_range = 0
+
+    @property
+    def size(self):
+        if not hasattr(self, '_size'):
+            self._size = self._storage.size(self._name)
+        return self._size
+
+    def read(self, num_bytes=None):
+        if num_bytes is None:
+            args = []
+            self.start_range = 0
+        else:
+            args = [self.start_range, self.start_range + num_bytes - 1]
+        data = self._storage._read(self._name, *args)
+        self.file = StringIO(data)
+        return self.file.getvalue()
+
+    def write(self, content):
+        if 'w' not in self._mode:
+            raise AttributeError("File was opened for read-only access.")
+        self.file = StringIO(content)
+        self._is_dirty = True
+
+    def close(self):
+        if self._is_dirty:
+            self._storage._save(self._name, self.file)
+        self.file.close()
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.