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