Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGonzalo Odiard <godiard@gmail.com>2013-03-18 17:39:53 (GMT)
committer Gonzalo Odiard <godiard@gmail.com>2013-03-18 17:39:53 (GMT)
commite71f326f4fde9ba88d9c1991a1b1520e55f71161 (patch)
tree11fadae1890f7cfacaae22bbc949e047c24ded27
parent74b7d8ff94a7c95e1c4c33247fdfddc95e451aeb (diff)
Upload a file from the joining instance to the server
Basic implementation. Have some limitations, as not be able to upload all the metadata at the moment. Metadata will be uploaded in annother commit. Signed-off-by: Gonzalo Odiard <gonzalo@laptop.org>
-rw-r--r--activity.py23
-rw-r--r--filepicker.py81
-rw-r--r--server.py150
-rw-r--r--web/index.html10
4 files changed, 258 insertions, 6 deletions
diff --git a/activity.py b/activity.py
index 61780bf..532e457 100644
--- a/activity.py
+++ b/activity.py
@@ -31,9 +31,11 @@ from sugar3.activity.widgets import StopButton
from sugar3.graphics.toolbarbox import ToolbarBox
import downloadmanager
+from filepicker import FilePicker
JOURNAL_STREAM_SERVICE = 'journal-activity-http'
+
class JournalShare(activity.Activity):
def __init__(self, handle):
@@ -41,10 +43,11 @@ class JournalShare(activity.Activity):
activity.Activity.__init__(self, handle)
activity_path = activity.get_bundle_path()
+ activity_root = activity.get_activity_root()
#TODO: check available port
self.port = 2500
self.server_proc = subprocess.Popen(['/bin/python', 'server.py',
- activity_path, str(self.port)])
+ activity_path, activity_root, str(self.port)])
toolbar_box = ToolbarBox()
@@ -70,6 +73,12 @@ class JournalShare(activity.Activity):
self.__mime_type_policy_cb)
self.view.connect('download-requested', self.__download_requested_cb)
+ try:
+ self.view.connect('run-file-chooser', self.__run_file_chooser)
+ except TypeError:
+ # Only present in WebKit1 > 1.9.3 and WebKit2
+ pass
+
self.view.load_uri('http://localhost:2500/web/index.html')
self.view.show()
scrolled = Gtk.ScrolledWindow()
@@ -188,6 +197,18 @@ class JournalShare(activity.Activity):
return False
+ def __run_file_chooser(self, browser, request):
+ picker = FilePicker(self)
+ chosen = picker.run()
+ picker.destroy()
+
+ if chosen:
+ request.select_files([chosen])
+ elif hasattr(request, 'cancel'):
+ # WebKit2 only
+ request.cancel()
+ return True
+
def __download_requested_cb(self, browser, download):
downloadmanager.add_download(download, browser)
return True
diff --git a/filepicker.py b/filepicker.py
new file mode 100644
index 0000000..65ac464
--- /dev/null
+++ b/filepicker.py
@@ -0,0 +1,81 @@
+# Copyright (C) 2007, One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+import os
+import tempfile
+import shutil
+
+from gi.repository import Gtk
+
+from sugar3.graphics.objectchooser import ObjectChooser
+from sugar3.activity.activity import get_activity_root
+
+
+_temp_dirs_to_clean = []
+
+
+def cleanup_temp_files():
+ while _temp_dirs_to_clean:
+ temp_dir = _temp_dirs_to_clean.pop()
+ if os.path.isdir(temp_dir):
+ shutil.rmtree(temp_dir, ignore_errors=True)
+ else:
+ logging.debug('filepicker.cleanup_temp_files: no file %r',
+ temp_dir)
+
+
+class FilePicker(ObjectChooser):
+ def __init__(self, parent):
+ ObjectChooser.__init__(self, parent)
+
+ def run(self):
+ jobject = None
+ _file = None
+ try:
+ result = ObjectChooser.run(self)
+ if result == Gtk.ResponseType.ACCEPT:
+ jobject = self.get_selected_object()
+ logging.debug('FilePicker.show: %r', jobject)
+
+ if jobject and jobject.file_path:
+ tmp_dir = tempfile.mkdtemp(prefix='', \
+ dir=os.path.join(get_activity_root(), 'tmp'))
+ _file = os.path.join(tmp_dir, _basename_strip(jobject))
+
+ os.rename(jobject.file_path, _file)
+
+ global _temp_dirs_to_clean
+ _temp_dirs_to_clean.append(tmp_dir)
+
+ logging.debug('FilePicker.show: file=%r', _file)
+ finally:
+ if jobject is not None:
+ jobject.destroy()
+
+ return _file
+
+
+def _basename_strip(jobject):
+ name = jobject.metadata.get('title', 'untitled')
+ name = name.replace(os.sep, ' ').strip()
+
+ root_, mime_extension = os.path.splitext(jobject.file_path)
+
+ if not name.endswith(mime_extension):
+ name += mime_extension
+
+ return name
diff --git a/server.py b/server.py
index 05f4014..80046fd 100644
--- a/server.py
+++ b/server.py
@@ -18,11 +18,110 @@ import os
import sys
import logging
import json
+import cgi
+import magic
from gi.repository import Gio
from sugar3 import network
from sugar3.datastore import datastore
+from warnings import filterwarnings, catch_warnings
+with catch_warnings():
+ if sys.py3kwarning:
+ filterwarnings("ignore", ".*mimetools has been removed",
+ DeprecationWarning)
+ import mimetools
+
+# Maximum input we will accept when REQUEST_METHOD is POST
+# 0 ==> unlimited input
+maxlen = 0
+
+
+def parse_multipart(fp, pdict):
+ """Parse multipart input.
+ Copied from cgi.py , but modified to get the filename
+ Arguments:
+ fp : input file
+ pdict: dictionary containing other parameters of content-type header
+ filenamedict: dictionary containing filenames if available
+ """
+ boundary = ""
+ if 'boundary' in pdict:
+ boundary = pdict['boundary']
+ if not cgi.valid_boundary(boundary):
+ raise ValueError('Invalid boundary in multipart form: %r' % boundary)
+
+ nextpart = "--" + boundary
+ lastpart = "--" + boundary + "--"
+ partdict = {}
+ filenamesdict = {}
+ terminator = ""
+
+ while terminator != lastpart:
+ bytes = -1
+ data = None
+ if terminator:
+ # At start of next part. Read headers first.
+ headers = mimetools.Message(fp)
+ clength = headers.getheader('content-length')
+ if clength:
+ try:
+ bytes = int(clength)
+ except ValueError:
+ pass
+ if bytes > 0:
+ if maxlen and bytes > maxlen:
+ raise ValueError('Maximum content length exceeded')
+ data = fp.read(bytes)
+ else:
+ data = ""
+ # Read lines until end of part.
+ lines = []
+ while 1:
+ line = fp.readline()
+ if not line:
+ terminator = lastpart # End outer loop
+ break
+ if line[:2] == "--":
+ terminator = line.strip()
+ if terminator in (nextpart, lastpart):
+ break
+ lines.append(line)
+ # Done with part.
+ if data is None:
+ continue
+ if bytes < 0:
+ if lines:
+ # Strip final line terminator
+ line = lines[-1]
+ if line[-2:] == "\r\n":
+ line = line[:-2]
+ elif line[-1:] == "\n":
+ line = line[:-1]
+ lines[-1] = line
+ data = "".join(lines)
+ line = headers['content-disposition']
+ if not line:
+ continue
+ else:
+ logging.error('CONTENT DISPOSITION %s', line)
+ key, params = cgi.parse_header(line)
+ if key != 'form-data':
+ continue
+ if 'name' in params:
+ name = params['name']
+ else:
+ continue
+ if 'filename' in params:
+ filenamesdict[name] = params['filename']
+
+ if name in partdict:
+ partdict[name].append(data)
+ else:
+ partdict[name] = [data]
+
+ return partdict, filenamesdict
+
class JournalHTTPRequestHandler(network.ChunkedGlibHTTPRequestHandler):
"""HTTP Request Handler to send data to the webview.
@@ -37,6 +136,45 @@ class JournalHTTPRequestHandler(network.ChunkedGlibHTTPRequestHandler):
self.send_header("Content-type", "text/html")
self.end_headers()
+ def do_POST(self):
+ if self.path == '/datastore/upload':
+ ctype = self.headers.get('content-type')
+ if not ctype:
+ return None
+ ctype, pdict = cgi.parse_header(ctype)
+ file_fields, filenames = parse_multipart(self.rfile, pdict)
+ file_content = file_fields['journal_item'][0]
+ logging.error('CONTENT %s', file_content)
+ file_name = filenames['journal_item']
+ logging.error('NAME %s', file_name)
+ # save to the journal
+ new_dsobject = datastore.create()
+ file_path = os.path.join(self.server.activity_root, 'instance',
+ file_name)
+ f = open(file_path, 'w')
+ try:
+ f.write(file_content)
+ finally:
+ f.close()
+ #Set the file_path in the datastore.
+ new_dsobject.set_file_path(file_path)
+ new_dsobject.metadata['title'] = file_name
+ # get mime type
+ m = magic.open(magic.MAGIC_MIME)
+ m.load()
+ mime_type = m.file(file_path)
+ if mime_type.find(';') > 0:
+ # can be 'application/ogg; charset=binary'
+ mime_type = mime_type[:mime_type.find(';')]
+ new_dsobject.metadata['mime_type'] = mime_type
+ # mark as favorite
+ new_dsobject.metadata['keep'] = '1'
+ datastore.write(new_dsobject)
+ #redirect to index.html page
+ self.send_response(301)
+ self.send_header('Location', '/web/index.html')
+ self.end_headers()
+
def do_GET(self):
"""Respond to a GET request."""
#logging.error('inside do_get dir(self) %s', dir(self))
@@ -89,10 +227,11 @@ class JournalHTTPRequestHandler(network.ChunkedGlibHTTPRequestHandler):
class JournalHTTPServer(network.GlibTCPServer):
"""HTTP Server for transferring document while collaborating."""
- def __init__(self, server_address, activity_path):
+ def __init__(self, server_address, activity_path, activity_root):
"""Set up the GlibTCPServer with the JournalHTTPRequestHandler.
"""
self.activity_path = activity_path
+ self.activity_root = activity_root
network.GlibTCPServer.__init__(self, server_address,
JournalHTTPRequestHandler)
@@ -153,15 +292,16 @@ class JournalManager():
return json.dumps(results)
-def setup_server(activity_path, port):
- server = JournalHTTPServer(("", port), activity_path)
+def setup_server(activity_path, activity_root, port):
+ server = JournalHTTPServer(("", port), activity_path, activity_root)
return server
if __name__ == "__main__":
activity_path = sys.argv[1]
- port = int(sys.argv[2])
- server = setup_server(activity_path, port)
+ activity_root = sys.argv[2]
+ port = int(sys.argv[3])
+ server = setup_server(activity_path, activity_root, port)
try:
logging.debug("Before start server")
server.serve_forever()
diff --git a/web/index.html b/web/index.html
index eba0432..413ca0c 100644
--- a/web/index.html
+++ b/web/index.html
@@ -24,6 +24,16 @@
</script>
</head>
<body>
+ <table>
+ <tr><td>
+ <form action="/datastore/upload" method="post" enctype="multipart/form-data">
+ Add from my Journal:<br>
+ <input type="file" name="journal_item" size="30">
+ <input type="submit" value="Send">
+ </form>
+ </td></tr>
+ </table>
+
<table id="journaltable">
</table>