3 # This file is part of MaePadWeb
4 # Copyright (c) 2010 Thomas Perl <thp.io/about>
5 # http://thp.io/2010/maepad/
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 os
.chdir(os
.path
.dirname(__file__
) or '.')
25 sys
.path
.insert(0, os
.path
.dirname(__file__
) or '.')
42 import simplejson
as json
47 PIXMAPS
= '/usr/share/pixmaps/maepad/'
48 THEME_BASE
= '/etc/hildon/theme/images/'
50 'toolbar_hi': THEME_BASE
+ 'toolbar_button_pressed.png',
51 'toolbar_bg': THEME_BASE
+ 'ToolbarPrimaryBackground.png',
52 'title_bg': THEME_BASE
+ 'wmTitleBar.png',
53 'title_btn': THEME_BASE
+ 'wmBackIcon.png',
58 # The data of a fully transparent 1x1 GIF
59 NIX_GIF
= 'R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='.decode('base64')
62 UNKNOWN
, TEXT
, SKETCH
, CHECKLIST
= range(4)
64 class NodeFlag(object):
66 SKETCHLINES
, SKETCHGRAPH
, WORDRAP
= (1 << x
for x
in range(3))
68 class ChecklistStyle(object):
69 CHECKED
, BOLD
, STRIKE
= (1 << x
for x
in range(3))
71 class checklists(object):
83 c
= lambda a
, b
: cmp((a
.ord, a
.idx
), (b
.ord, b
.idx
))
103 c
= lambda a
, b
: cmp(a
.ord, b
.ord)
107 class MaePadServer(BaseHTTPServer
.BaseHTTPRequestHandler
):
112 def start_output(self
, status
=200, content_type
='text/html'):
113 self
.send_response(status
)
114 self
.send_header('Content-type', content_type
)
116 self
.send_header('WWW-Authenticate', 'Basic realm="MaePadWeb"')
119 def send_as(self
, data
, mimetype
):
120 self
.start_output(content_type
=mimetype
)
121 self
.wfile
.write(data
)
124 send_html
= lambda s
, d
: s
.send_as(d
, 'text/html')
125 send_js
= lambda s
, d
: s
.send_as(d
, 'text/javascript')
126 send_json
= lambda s
, d
: s
.send_as(json
.dumps(d
), 'text/plain')
127 send_png
= lambda s
, d
: s
.send_as(d
, 'image/png')
128 send_gif
= lambda s
, d
: s
.send_as(d
, 'image/gif')
130 def _nodelist__json(self
):
131 def get_nodes(parent
=0):
132 return [(x
.nodeid
, x
.name
, x
.bodytype
, get_nodes(x
.nodeid
)) \
133 for x
in self
.db
.load(MaePad
.nodes
, parent
=parent
)]
134 self
.send_json(get_nodes())
136 def _checklist__json(self
, id):
137 c
= lambda x
: (x
.idx
, x
.name
, x
.style
, '%06x' % x
.color
)
138 self
.send_json([c(x
) for x
in self
.db
.load(MaePad
.checklists
, nodeid
=id)])
140 def _checklist__setBold(self
, id, bold
):
141 bold
= (bold
== 'true')
142 element
= self
.db
.get(MaePad
.checklists
, idx
=id)
144 newStyle
= element
.style | MaePad
.ChecklistStyle
.BOLD
146 newStyle
= element
.style
& ~
(MaePad
.ChecklistStyle
.BOLD
)
147 self
.db
.update(element
, style
=newStyle
)
150 def _checklist__setStrike(self
, id, strike
):
151 strike
= (strike
== 'true')
152 element
= self
.db
.get(MaePad
.checklists
, idx
=id)
154 newStyle
= element
.style | MaePad
.ChecklistStyle
.STRIKE
156 newStyle
= element
.style
& ~
(MaePad
.ChecklistStyle
.STRIKE
)
157 self
.db
.update(element
, style
=newStyle
)
160 def _checklist__setCheck(self
, id, check
):
161 check
= (check
== 'true')
162 element
= self
.db
.get(MaePad
.checklists
, idx
=id)
164 newStyle
= element
.style | MaePad
.ChecklistStyle
.CHECKED
166 newStyle
= element
.style
& ~
(MaePad
.ChecklistStyle
.CHECKED
)
167 self
.db
.update(element
, style
=newStyle
)
170 def _checklist__setText(self
, id, text
):
171 element
= self
.db
.get(MaePad
.checklists
, idx
=id)
172 self
.db
.update(element
, name
=text
)
175 def _checklist__addItem(self
, id, text
):
177 checklist
= MaePad
.checklists()
178 checklist
.nodeid
= int(id)
179 checklist
.name
= text
183 self
.db
.save(checklist
)
187 self
.send_gif(MaePad
.NIX_GIF
)
189 def _checklist__deleteItem(self
, id):
190 self
.db
.delete(MaePad
.checklists
, idx
=id)
193 def _sketch__png(self
, id):
194 self
.send_png(self
.db
.get(MaePad
.nodes
, nodeid
=id).bodyblob
)
196 def _richtext__html(self
, id):
197 self
.send_html(self
.db
.get(MaePad
.nodes
, nodeid
=id).bodyblob
)
199 def send_icon(self
, icon_name
):
200 theme
= gtk
.icon_theme_get_default()
201 icon
= theme
.lookup_icon(icon_name
, 32, 0)
203 filename
= os
.path
.join(MaePad
.PIXMAPS
,\
206 filename
= icon
.get_filename()
208 self
.send_png(open(filename
, 'rb').read())
210 def send_theme(self
, theme_part
):
211 if theme_part
in MaePad
.THEME
:
212 self
.send_png(open(MaePad
.THEME
[theme_part
], 'rb').read())
214 def is_valid_auth(self
, auth
):
217 if not auth
.startswith('Basic '):
220 auth
= auth
[len('Basic '):].decode('base64')
224 username
, password
= auth
.split(':', 1)
225 return (password
== MaePadServer
.password
)
227 def client_is_local(self
):
228 host
, port
= self
.client_address
229 return host
.startswith('127.')
232 url
= urlparse
.urlparse(self
.path
)
233 query
= cgi
.parse_qs(url
.query
)
234 query
= dict((x
, y
[-1]) for x
, y
in query
.items())
235 path
= filter(None, url
.path
.split('/'))
241 elif len(path
) == 2 and path
[0] == 'icons':
242 return self
.send_icon(path
[1])
243 elif len(path
) == 2 and path
[0] == 'theme':
244 return self
.send_theme(path
[1])
246 self
.start_output(404, 'text/plain')
247 self
.wfile
.write("404'd!")
250 if MaePadServer
.password
and not self
.client_is_local():
251 auth
= self
.headers
.get('Authorization', '')
252 if not self
.is_valid_auth(auth
):
253 self
.start_output(401, 'text/plain')
254 self
.wfile
.write('please authenticate.')
259 self
.send_html(open('index.html').read())
260 elif mode
== 'jquery.js':
261 self
.send_js(open('jquery-1.4.3.min.js').read())
262 elif mode
== 'logo.png':
263 self
.send_png(open('logo.png').read())
265 attr_name
= '_' + mode
.replace('.', '__')
266 if hasattr(self
, attr_name
):
267 getattr(self
, attr_name
)(**query
)
269 client
= gconf
.client_get_default()
270 database_file
= client
.get_string('/apps/maepad/general/lastdocument') or 'memos.db'
272 MaePadServer
.db
= minidb
.Store(database_file
)
275 def server_thread_proc():
276 server
= BaseHTTPServer
.HTTPServer(('', PORT
), MaePadServer
)
280 while not MaePadServer
.close
:
281 server
.handle_request()
286 t
= threading
.Thread(target
=server_thread_proc
)
291 p
= subprocess
.Popen(['/sbin/ifconfig'], stdout
=subprocess
.PIPE
)
292 stdout
, stderr
= p
.communicate()
294 return [ip
for ip
, rest
in re
.findall(r
'addr:((\d+\.){3}\d+)', stdout
) if not ip
.startswith('127.')]
297 w
.set_title('MaePadWeb Server')
300 vb
.set_border_width(10)
304 sw
= hildon
.PannableArea()
306 print >>sys
.stderr
, e
307 sw
= gtk
.ScrolledWindow()
310 sw
.add_with_viewport(vb
)
312 def make_button(title
, value
=None):
315 b
= hildon
.Button(gtk
.HILDON_SIZE_THUMB_HEIGHT | gtk
.HILDON_SIZE_AUTO_WIDTH
, 0)
321 return gtk
.Button(title
)
323 def generate_password():
324 # http://stackoverflow.com/questions/3854692
325 chars
= string
.letters
+ string
.digits
327 return ''.join(random
.choice(chars
) for _
in xrange(length
))
330 def open_website(url
):
332 context
= osso
.Context('MaePadWeb', '1.0', False)
333 rpc
= osso
.Rpc(context
)
334 rpc
.rpc_run_with_defaults('osso_browser', \
338 def on_start_server_clicked(button
):
339 for child
in vb
.get_children():
342 # Generate a random password
343 MaePadServer
.password
= generate_password()
346 vb
.add(gtk
.Label('Server running on port %d. Close window to stop server.' % PORT
))
347 vb
.add(gtk
.Label('DB file: ' + database_file
))
348 vb
.add(gtk
.Label('Your IP(s): ' + ', '.join(get_ips())))
350 url
= 'http://%s:%s/' % (ip
, PORT
)
351 b
= make_button('Open in web browser', url
)
352 b
.connect('clicked', lambda b
, url
: open_website(url
), url
)
353 vb
.pack_start(b
, expand
=False)
355 l
.set_markup('Username: <i>any</i>, Password: <b><tt>%s</tt></b>' % MaePadServer
.password
)
359 b
= make_button('Start server', 'Start web server on port %d' % PORT
)
360 b
.connect('clicked', on_start_server_clicked
)
362 vb
.pack_start(b
, expand
=False)
363 vb
.pack_start(gtk
.Label(''))
364 vb
.pack_start(gtk
.Label('The service is not encrypted. For use in trusted networks only.'))
365 vb
.pack_start(gtk
.Label('Do not run MaePad and MaePadWeb simultaneously.'))
368 MaePadServer
.close
= True
371 w
.connect('destroy', on_destroy
)
374 gobject
.threads_init()
379 print >>sys
.stderr
, 'Caught exception; closing ('+str(e
)+')'
381 MaePadServer
.db
.commit()