Passed
Pull Request — master (#319)
by Jaisen
02:25 queued 12s
created

GooglePhotos.after()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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