7bb183f897797773f1baf88b4f965212192a8ab6
[oweals/karmaworld.git] / karmaworld / apps / notes / gdrive.py
1 #!/usr/bin/env python
2 # -*- coding:utf8 -*-
3 # Copyright (C) 2012  FinalsClub Foundation
4
5 import datetime
6 import logging
7 from django.contrib.auth.models import User
8 from django.conf import settings
9 from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
10 from karmaworld.apps.notes.models import UserUploadMapping
11 from karmaworld.apps.notes.models import NoteMarkdown
12 from karmaworld.apps.quizzes.models import Keyword
13 from karmaworld.apps.users.models import NoteKarmaEvent
14 import os
15 import subprocess
16 import tempfile
17 import uuid
18 import magic
19 import re
20 import json
21 import time
22
23 import httplib2
24 import html2text
25 from apiclient.discovery import build
26 from apiclient.http import MediaInMemoryUpload
27 from oauth2client.client import SignedJwtAssertionCredentials
28
29 import karmaworld.secret.drive as drive
30
31 logger = logging.getLogger(__name__)
32
33 PDF_MIMETYPE = 'application/pdf'
34 PPT_MIMETYPES = ['application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation']
35
36
37 def build_api_service():
38     """
39     Build and returns a Drive service object authorized with the service
40     accounts that act on behalf of the given user.
41
42     Will target the Google Drive of GOOGLE_USER email address.
43     Returns a Google Drive service object.
44
45     Code herein adapted from:
46     https://developers.google.com/drive/delegation
47     """
48
49     # Extract the service address from the client secret
50     with open(drive.CLIENT_SECRET, 'r') as fp:
51         service_user = json.load(fp)['web']['client_email']
52
53     # Pull in the service's p12 private key.
54     with open(drive.SERVICE_KEY, 'rb') as p12:
55         # Use the private key to auth as the service user for access to the
56         # Google Drive of the GOOGLE_USER
57         credentials = SignedJwtAssertionCredentials(service_user, p12.read(),
58                                scope='https://www.googleapis.com/auth/drive',
59                                sub=drive.GOOGLE_USER)
60
61     return build('drive', 'v2', http=credentials.authorize(httplib2.Http()))
62
63
64 def pdf2html(content):
65     pdf_file = tempfile.NamedTemporaryFile()
66     pdf_file.write(content)
67     pdf_file.flush()
68     tmp_dir = tempfile.gettempdir()
69     html_file_name = uuid.uuid4().hex
70     html_file_path = os.path.join(tmp_dir, html_file_name)
71
72     command = ['pdf2htmlEX', pdf_file.name, html_file_name]
73     devnull = open('/dev/null', 'w')
74     if settings.TESTING:
75         call = subprocess.Popen(command, shell=False, cwd=tmp_dir, stdout=devnull, stderr=devnull)
76     else:
77         call = subprocess.Popen(command, shell=False, cwd=tmp_dir)
78     call.wait()
79     devnull.close()
80     if call.returncode != 0:
81         raise ValueError("PDF file could not be processed")
82
83     pdf_file.close()
84
85     try:
86         html_file = open(html_file_path, 'r')
87         html = html_file.read()
88         html_file.close()
89         os.remove(html_file_path)
90     except IOError, e:
91         raise ValueError("PDF file could not be processed")
92
93     if len(html) == 0:
94         raise ValueError("PDF file results in empty HTML file")
95
96     return html
97
98
99 def download_from_gdrive(service, file_dict, extension=None, mimetype=None):
100     """ Take in a gdrive service, file_dict from upload, and either an
101         extension or mimetype.
102         You must provide an `extension` or `mimetype`
103         Returns contextual files from google
104     """
105     download_urls = {}
106     download_urls['text'] = file_dict[u'exportLinks']['text/plain']
107
108     if extension:
109         extension = extension.lower()
110
111     if extension in ['.ppt', 'pptx'] \
112         or mimetype in PPT_MIMETYPES:
113         download_urls['pdf'] = file_dict[u'exportLinks']['application/pdf']
114     elif mimetype == PDF_MIMETYPE:
115         pass
116     else:
117         download_urls['html'] = file_dict[u'exportLinks']['text/html']
118
119     content_dict = {}
120     for download_type, download_url in download_urls.items():
121         print "\n%s -- %s" % (download_type, download_url)
122         resp, content = service._http.request(download_url)
123
124         if resp.status in [200]:
125             print "\t downloaded!"
126             # save to the File.property resulting field
127             content_dict[download_type] = content
128         else:
129             print "\t Download failed: %s" % resp.status
130
131     return content_dict
132
133
134 def upload_to_gdrive(service, media, filename, extension=None, mimetype=None):
135     """ take a gdrive service object, and a media wrapper and upload to gdrive
136         returns a file_dict
137         You must provide an `extension` or `mimetype`
138     """
139     _resource = {'title': filename}
140
141     # clean up extensions for type checking
142     if extension:
143         extension = extension.lower()
144
145     # perform OCR on files that are image intensive
146     ocr = extension in ['.pdf', '.jpeg', '.jpg', '.png'] or \
147           mimetype in ['application/pdf']
148
149     file_dict = service.files().insert(body=_resource, media_body=media,\
150                                        convert=True, ocr=ocr).execute()
151
152     # increase exponent of 2 for exponential growth.
153     # 2 ** -1 = 0.5, 2 ** 0 = 1, 2 ** 1 = 2, 4, 8, 16, ...
154     delay_exp = -1
155     # exponentially wait for exportLinks to be returned if missing
156     while u'exportLinks' not in file_dict or \
157           u'text/plain' not in file_dict[u'exportLinks']:
158         # if a bunch  seconds have passed, give up
159         if delay_exp == 7:
160             raise ValueError('Google Drive failed to read the document.')
161
162         # wait some seconds
163         print "upload_check_sleep({0})".format(2. ** delay_exp)
164         time.sleep(2. ** delay_exp)
165         delay_exp = delay_exp + 1
166
167         # try to get the doc from gdrive
168         file_dict = service.files().get(fileId=file_dict[u'id']).execute()
169
170     return file_dict
171
172
173 def convert_raw_document(raw_document, user=None):
174     """ Upload a raw document to google drive and get a Note back"""
175     fp_file = raw_document.get_file()
176
177     # extract some properties from the document metadata
178     filename = raw_document.name
179     print "this is the mimetype of the document to check:"
180     mimetype = raw_document.mimetype
181     print mimetype
182     print ""
183
184     # A special case for Evernotes
185     if raw_document.mimetype == 'text/enml':
186         raw_document.mimetype = 'text/html'
187
188     original_content = fp_file.read()
189
190     # Include mimetype parameter if there is one to include
191     extra_flags = {'mimetype': raw_document.mimetype} if raw_document.mimetype \
192                   else {}
193     media = MediaInMemoryUpload(original_content, chunksize=1024*1024, \
194                                 resumable=True, **extra_flags)
195
196
197     service = build_api_service()
198
199     # upload to google drive
200     file_dict = upload_to_gdrive(service, media, filename, mimetype=mimetype)
201
202     # download from google drive
203     content_dict = download_from_gdrive(service, file_dict, mimetype=mimetype)
204
205     # this should have already happened, lets see why it hasn't
206     raw_document.is_processed = True
207     raw_document.save()
208
209     note = raw_document.convert_to_note()
210
211     # Cache the uploaded file's URL
212     note.gdrive_url = file_dict['alternateLink']
213
214     # Extract HTML from the appropriate place
215     html = ''
216     convert_to_markdown = False
217     if raw_document.mimetype == PDF_MIMETYPE:
218         html = pdf2html(original_content)
219     elif raw_document.mimetype in PPT_MIMETYPES:
220         html = pdf2html(content_dict['pdf'])
221     elif 'html' in content_dict and content_dict['html']:
222         html = content_dict['html']
223         convert_to_markdown = True
224     # cleanup the HTML
225     html = note.filter_html(html)
226
227     # upload the HTML file to static host if it is not already there
228     note.send_to_s3(html, do_save=False)
229
230     note.text = content_dict['text']
231
232     if convert_to_markdown:
233         h = html2text.HTML2Text()
234         h.google_doc = True
235         h.escape_snob = True
236         h.unicode_snob = True
237         markdown = h.handle(html.decode('utf8', 'ignore'))
238
239         note_markdown = NoteMarkdown(note=note, markdown=markdown)
240         note_markdown.save()
241
242     # If we know the user who uploaded this,
243     # associate them with the note
244     if user:
245         note.user = user
246         NoteKarmaEvent.create_event(user, note, NoteKarmaEvent.UPLOAD)
247     else:
248         try:
249             mapping = UserUploadMapping.objects.get(fp_file=raw_document.fp_file)
250             note.user = mapping.user
251             note.save()
252             NoteKarmaEvent.create_event(mapping.user, note, NoteKarmaEvent.UPLOAD)
253         except (ObjectDoesNotExist, MultipleObjectsReturned):
254             logger.info("Zero or multiple mappings found with fp_file " + raw_document.fp_file.name)
255
256     # Finally, save whatever data we got back from google
257     note.save()
258
259
260
261
262