Passed
Pull Request — master (#319)
by Jaisen
01:48
created

GooglePhotos.upload()   C

Complexity

Conditions 11

Size

Total Lines 35
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 26
nop 2
dl 0
loc 35
rs 5.4
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like elodie.plugins.googlephotos.googlephotos.GooglePhotos.upload() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""
2
Google Photos plugin object.
3
Upload code adapted from https://github.com/eshmu/gphotos-upload
4
5
.. moduleauthor:: Jaisen Mathai <[email protected]>
6
"""
7
from __future__ import print_function
8
9
import json
10
11
from os.path import basename, isfile
12
13
from google_auth_oauthlib.flow import InstalledAppFlow
14
from google.auth.transport.requests import AuthorizedSession
15
from google.oauth2.credentials import Credentials
16
17
from elodie.media.photo import Photo
18
from elodie.media.video import Video
19
from elodie.plugins.plugins import PluginBase
20
21
class GooglePhotos(PluginBase):
22
    """A class to execute plugin actions.
23
       
24
       Requires a config file with the following configurations set.
25
       secrets_file:
26
            The full file path where to find the downloaded secrets.
27
       auth_file:
28
            The full file path where to store authenticated tokens.
29
    
30
    """
31
32
    __name__ = 'GooglePhotos'
33
34
    def __init__(self):
35
        super(GooglePhotos, self).__init__()
36
        self.upload_url = 'https://photoslibrary.googleapis.com/v1/uploads'
37
        self.media_create_url = 'https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate'
38
        self.scopes = [
39
            'https://www.googleapis.com/auth/photoslibrary',
40
            'https://www.googleapis.com/auth/photoslibrary.appendonly',
41
            'https://www.googleapis.com/auth/photoslibrary.sharing'
42
        ]
43
        
44
        self.secrets_file = None
45
        if('secrets_file' in self.config_for_plugin):
46
            self.secrets_file = self.config_for_plugin['secrets_file']
47
        # 'client_id.json'
48
        self.auth_file = None
49
        if('auth_file' in self.config_for_plugin):
50
            self.auth_file = self.config_for_plugin['auth_file']
51
        self.session = None
52
53
    def after(self, file_path, destination_folder, final_file_path, metadata):
54
        extension = metadata['extension']
55
        if(extension in Photo.extensions or extension in Video.extensions):
56
            self.log(u'Added {} to db.'.format(final_file_path))
57
            self.db.set(final_file_path, metadata['original_name'])
58
        else:
59
            self.log(u'Skipping {} which is not a supported media type.'.format(final_file_path))
60
61
    def batch(self):
62
        queue = self.db.get_all()
63
        status = True
64
        count = 0
65
        for key in queue:
66
            this_status = self.upload(key)
67
            if(this_status):
68
                # Remove from queue if successful then increment count
69
                self.db.delete(key)
70
                count = count + 1
71
                self.display('{} uploaded successfully.'.format(key))
72
            else:
73
                status = False
74
                self.display('{} failed to upload.'.format(key))
75
        return (status, count)
76
77
    def before(self, file_path, destination_folder):
78
        pass
79
80
    def set_session(self):
81
        # Try to load credentials from an auth file.
82
        # If it doesn't exist or is not valid then catch the 
83
        #  exception and reauthenticate.
84
        try:
85
            creds = Credentials.from_authorized_user_file(self.auth_file, self.scopes)
86
        except:
87
            flow = InstalledAppFlow.from_client_secrets_file(self.secrets_file, self.scopes)
88
            creds = flow.run_local_server()
89
            cred_dict = {
90
                'token': creds.token,
91
                'refresh_token': creds.refresh_token,
92
                'id_token': creds.id_token,
93
                'scopes': creds.scopes,
94
                'token_uri': creds.token_uri,
95
                'client_id': creds.client_id,
96
                'client_secret': creds.client_secret
97
            }
98
99
            # Store the returned authentication tokens to the auth_file.
100
            with open(self.auth_file, 'w') as f:
101
                f.write(json.dumps(cred_dict))
102
103
        self.session = AuthorizedSession(creds)
104
        self.session.headers["Content-type"] = "application/octet-stream"
105
        self.session.headers["X-Goog-Upload-Protocol"] = "raw"
106
107
    def upload(self, path_to_photo):
108
        self.set_session()
109
        if(self.session is None):
110
            self.log('Could not initialize session')
111
            return None
112
113
        self.session.headers["X-Goog-Upload-File-Name"] = basename(path_to_photo)
114
        if(not isfile(path_to_photo)):
115
            self.log('Could not find file: {}'.format(path_to_photo))
116
            return None
117
118
        with open(path_to_photo, 'rb') as f:
119
            photo_bytes = f.read()
120
121
        upload_token = self.session.post(self.upload_url, photo_bytes)
122
        if(upload_token.status_code != 200 or not upload_token.content):
123
            self.log('Uploading media failed: ({}) {}'.format(upload_token.status_code, upload_token.content))
124
            return None
125
126
        create_body = json.dumps({'newMediaItems':[{'description':'','simpleMediaItem':{'uploadToken':upload_token.content.decode()}}]}, indent=4)
127
        resp = self.session.post(self.media_create_url, create_body).json()
128
        if(
129
            'newMediaItemResults' not in resp or
130
            'status' not in resp['newMediaItemResults'][0] or
131
            'message' not in resp['newMediaItemResults'][0]['status'] or
132
            (
133
                resp['newMediaItemResults'][0]['status']['message'] != 'Success' and # photos
134
                resp['newMediaItemResults'][0]['status']['message'] != 'OK' # videos
135
            )
136
137
        ):
138
            self.log('Creating new media item failed: {}'.format(resp['newMediaItemResults'][0]['status']))
139
            return None
140
        
141
        return resp['newMediaItemResults'][0]
142