GooglePhotos.upload()   C
last analyzed

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