chiark / gitweb /
More documentation.
[cura.git] / Cura / util / youmagine.py
1 """
2 YouMagine communication module.
3 This module handles all communication with the YouMagine API.
4 """
5 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
6
7 import json
8 import httplib as httpclient
9 import urllib
10
11 class httpUploadDataStream(object):
12         """
13         For http uploads we need a readable/writable datasteam to use with the httpclient.HTTPSConnection class.
14         This is used to facilitate file uploads towards Youmagine.
15         """
16         def __init__(self, progressCallback):
17                 self._dataList = []
18                 self._totalLength = 0
19                 self._readPos = 0
20                 self._progressCallback = progressCallback
21
22         def write(self, data):
23                 size = len(data)
24                 if size < 1:
25                         return
26                 blocks = size / 2048
27                 for n in xrange(0, blocks):
28                         self._dataList.append(data[n*2048:n*2048+2048])
29                 self._dataList.append(data[blocks*2048:])
30                 self._totalLength += size
31
32         def read(self, size):
33                 if self._readPos >= len(self._dataList):
34                         return None
35                 ret = self._dataList[self._readPos]
36                 self._readPos += 1
37                 if self._progressCallback is not None:
38                         self._progressCallback(float(self._readPos / len(self._dataList)))
39                 return ret
40
41         def __len__(self):
42                 return self._totalLength
43
44 class Youmagine(object):
45         """
46         Youmagine connection object. Has various functions to communicate with Youmagine.
47         These functions are blocking and thus this class should be used from a thread.
48         """
49         def __init__(self, authToken, progressCallback = None):
50                 self._hostUrl = 'api.youmagine.com'
51                 self._viewUrl = 'www.youmagine.com'
52                 self._authUrl = 'https://www.youmagine.com/integrations/cura/authorized_integrations/new'
53                 self._authToken = authToken
54                 self._userName = None
55                 self._userID = None
56                 self._http = None
57                 self._hostReachable = True
58                 self._progressCallback = progressCallback
59                 self._categories = [
60                         ('Art', 2),
61                         ('Fashion', 3),
62                         ('For your home', 4),
63                         ('Gadget', 5),
64                         ('Games', 6),
65                         ('Jewelry', 7),
66                         ('Maker/DIY', 8),
67                         ('Miniatures', 9),
68                         ('Toys', 10),
69                         ('3D printer parts and enhancements', 11),
70                         ('Other', 1),
71                 ]
72                 self._licenses = [
73                         ('Creative Commons - Attribution Share Alike', 'ccbysa'),
74                         ('Creative Commons - Attribution Non-Commercial ShareAlike', 'ccbyncsa'),
75                         ('Creative Commons - Attribution No Derivatives', 'ccbynd'),
76                         ('Creative Commons - Attribution Non-Commercial No Derivatives', 'ccbyncsa'),
77                         ('GPLv3', 'gplv3'),
78                 ]
79
80         def getAuthorizationUrl(self):
81                 return self._authUrl
82
83         def getCategories(self):
84                 return map(lambda n: n[0], self._categories)
85
86         def getLicenses(self):
87                 return map(lambda n: n[0], self._licenses)
88
89         def setAuthToken(self, token):
90                 self._authToken = token
91                 self._userName = None
92                 self._userID = None
93
94         def getAuthToken(self):
95                 return self._authToken
96
97         def isHostReachable(self):
98                 return self._hostReachable
99
100         def viewUrlForDesign(self, id):
101                 return 'https://%s/designs/%d' % (self._viewUrl, id)
102
103         def editUrlForDesign(self, id):
104                 return 'https://%s/designs/%d/edit' % (self._viewUrl, id)
105
106         def isAuthorized(self):
107                 if self._authToken is None:
108                         return False
109                 if self._userName is None:
110                         #No username yet, try to request the username to see if the authToken is valid.
111                         result = self._request('GET', '/authorized_integrations/%s/whoami.json' % (self._authToken))
112
113                         if 'error' in result:
114                                 self._authToken = None
115                                 return False
116                         self._userName = result['screen_name']
117                         self._userID = result['id']
118                 return True
119
120         def createDesign(self, name, description, category, license):
121                 res = self._request('POST', '/designs.json', {'design[name]': name, 'design[excerpt]': description, 'design[design_category_id]': filter(lambda n: n[0] == category, self._categories)[0][1], 'design[license]': filter(lambda n: n[0] == license, self._licenses)[0][1]})
122                 if 'id' in res:
123                         return res['id']
124                 print res
125                 return None
126
127         def publishDesign(self, id):
128                 res = self._request('PUT', '/designs/%d/mark_as/publish.json' % (id), {'ignore': 'me'})
129                 if res is not None:
130                         return False
131                 return True
132
133         def createDocument(self, designId, name, contents):
134                 res = self._request('POST', '/designs/%d/documents.json' % (designId), {'document[name]': name, 'document[description]': 'Uploaded from Cura'}, {'document[file]': (name, contents)})
135                 if 'id' in res:
136                         return res['id']
137                 print res
138                 return None
139
140         def createImage(self, designId, name, contents):
141                 res = self._request('POST', '/designs/%d/images.json' % (designId), {'image[name]': name, 'image[description]': 'Uploaded from Cura'}, {'image[file]': (name, contents)})
142                 if 'id' in res:
143                         return res['id']
144                 print res
145                 return None
146
147         def listDesigns(self):
148                 res = self._request('GET', '/users/%s/designs.json' % (self._userID))
149                 return res
150
151         def _request(self, method, url, postData = None, files = None):
152                 retryCount = 2
153                 if self._authToken is not None:
154                         url += '?auth_token=%s' % (self._authToken)
155                 error = 'Failed to connect to %s' % self._hostUrl
156                 for n in xrange(0, retryCount):
157                         if self._http is None:
158                                 self._http = httpclient.HTTPSConnection(self._hostUrl)
159                         try:
160                                 if files is not None:
161                                         boundary = 'wL36Yn8afVp8Ag7AmP8qZ0SA4n1v9T'
162                                         s = httpUploadDataStream(self._progressCallback)
163                                         for k, v in files.iteritems():
164                                                 filename = v[0]
165                                                 fileContents = v[1]
166                                                 s.write('--%s\r\n' % (boundary))
167                                                 s.write('Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (k, filename))
168                                                 s.write('Content-Type: application/octet-stream\r\n')
169                                                 s.write('Content-Transfer-Encoding: binary\r\n')
170                                                 s.write('\r\n')
171                                                 s.write(fileContents)
172                                                 s.write('\r\n')
173
174                                         for k, v in postData.iteritems():
175                                                 s.write('--%s\r\n' % (boundary))
176                                                 s.write('Content-Disposition: form-data; name="%s"\r\n' % (k))
177                                                 s.write('\r\n')
178                                                 s.write(str(v))
179                                                 s.write('\r\n')
180                                         s.write('--%s--\r\n' % (boundary))
181
182                                         self._http.request(method, url, s, {"Content-type": "multipart/form-data; boundary=%s" % (boundary), "Content-Length": len(s)})
183                                 elif postData is not None:
184                                         self._http.request(method, url, urllib.urlencode(postData), {"Content-type": "application/x-www-form-urlencoded"})
185                                 else:
186                                         self._http.request(method, url)
187                         except IOError:
188                                 self._http.close()
189                                 continue
190                         try:
191                                 response = self._http.getresponse()
192                                 responseText = response.read()
193                         except:
194                                 self._http.close()
195                                 continue
196                         try:
197                                 if responseText == '':
198                                         return None
199                                 return json.loads(responseText)
200                         except ValueError:
201                                 print response.getheaders()
202                                 print responseText
203                                 error = 'Failed to decode JSON response'
204                 self._hostReachable = False
205                 return {'error': error}
206
207
208 class FakeYoumagine(Youmagine):
209         """
210         Fake Youmagine class to test without internet, acts the same as the YouMagine class, but without going to the internet.
211         Assists in testing UI features.
212         """
213         def __init__(self, authToken, callback):
214                 super(FakeYoumagine, self).__init__(authToken)
215                 self._authUrl = 'file:///C:/Models/output.html'
216                 self._authToken = None
217
218         def isAuthorized(self):
219                 if self._authToken is None:
220                         return False
221                 if self._userName is None:
222                         self._userName = 'FakeYoumagine'
223                         self._userID = '1'
224                 return True
225
226         def isHostReachable(self):
227                 return True
228
229         def createDesign(self, name, description, category, license):
230                 return 1
231
232         def publishDesign(self, id):
233                 pass
234
235         def createDocument(self, designId, name, contents):
236                 print "Create document: %s" % (name)
237                 f = open("C:/models/%s" % (name), "wb")
238                 f.write(contents)
239                 f.close()
240                 return 1
241
242         def createImage(self, designId, name, contents):
243                 print "Create image: %s" % (name)
244                 f = open("C:/models/%s" % (name), "wb")
245                 f.write(contents)
246                 f.close()
247                 return 1
248
249         def listDesigns(self):
250                 return []
251
252         def _request(self, method, url, postData = None, files = None):
253                 print "Err: Tried to do request: %s %s" % (method, url)