[Dextrose] [PATCH sugar 2/2] Initial client implementation for feedback feature
Aleksey Lim
alsroot at member.fsf.org
Mon Jan 17 21:53:45 EST 2011
---
bin/sugar-session | 8 +-
data/sugar.schemas.in | 44 +++++++
src/jarabe/model/Makefile.am | 1 +
src/jarabe/model/feedback_collector.py | 201 ++++++++++++++++++++++++++++++++
src/jarabe/model/shell.py | 16 +++
src/jarabe/view/service.py | 5 +
6 files changed, 274 insertions(+), 1 deletions(-)
create mode 100644 src/jarabe/model/feedback_collector.py
diff --git a/bin/sugar-session b/bin/sugar-session
index 0501311..d4362ef 100755
--- a/bin/sugar-session
+++ b/bin/sugar-session
@@ -229,7 +229,7 @@ def main():
gettext.textdomain('sugar')
from jarabe.desktop import homewindow
- from jarabe.model import sound
+ from jarabe.model import sound, feedback_collector
from jarabe import intro
logger.start('shell')
@@ -238,6 +238,12 @@ def main():
client.set_string('/apps/metacity/general/mouse_button_modifier',
'<Super>')
+ if client.get_bool('/desktop/sugar/feedback/enabled'):
+ feedback_collector.start(
+ client.get_string('/desktop/sugar/feedback/server_host'),
+ client.get_int('/desktop/sugar/feedback/server_port'),
+ client.get_int('/desktop/sugar/feedback/auto_submit_delay'))
+
timezone = client.get_string('/desktop/sugar/date/timezone')
if timezone is not None and timezone:
os.environ['TZ'] = timezone
diff --git a/data/sugar.schemas.in b/data/sugar.schemas.in
index a3817e9..880c9d1 100644
--- a/data/sugar.schemas.in
+++ b/data/sugar.schemas.in
@@ -2,6 +2,50 @@
<gconfschemafile>
<schemalist>
<schema>
+ <key>/schemas/desktop/sugar/feedback/enabled</key>
+ <applyto>/desktop/sugar/feedback/enabled</applyto>
+ <owner>sugar</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Enable of disable feedback feature</short>
+ <long>Setting value to false will disable any feedback reports.</long>
+ </locale>
+ </schema>
+ <schema>
+ <key>/schemas/desktop/sugar/feedback/server_host</key>
+ <applyto>/desktop/sugar/feedback/server_host</applyto>
+ <owner>sugar</owner>
+ <type>string</type>
+ <default>feedback.sugarlabs.org</default>
+ <locale name="C">
+ <short>Server host to send reports to</short>
+ <long>Server that will handle reports sent via HTTPS POST requests.</long>
+ </locale>
+ </schema>
+ <schema>
+ <key>/schemas/desktop/sugar/feedback/server_port</key>
+ <applyto>/desktop/sugar/feedback/server_port</applyto>
+ <owner>sugar</owner>
+ <type>int</type>
+ <default>80</default>
+ <locale name="C">
+ <short>Server port to send reports to</short>
+ <long>TCP port that will used to send HTTPS POST requests.</long>
+ </locale>
+ </schema>
+ <schema>
+ <key>/schemas/desktop/sugar/feedback/auto_submit_delay</key>
+ <applyto>/desktop/sugar/feedback/auto_submit_delay</applyto>
+ <owner>sugar</owner>
+ <type>int</type>
+ <default>0</default>
+ <locale name="C">
+ <short>Delay in seconds to send anonymous reports automatically</short>
+ <long>Setting value to 0 will disable automatic submiting.</long>
+ </locale>
+ </schema>
+ <schema>
<key>/schemas/desktop/sugar/user/nick</key>
<applyto>/desktop/sugar/user/nick</applyto>
<owner>sugar</owner>
diff --git a/src/jarabe/model/Makefile.am b/src/jarabe/model/Makefile.am
index 374d6fc..c221e86 100644
--- a/src/jarabe/model/Makefile.am
+++ b/src/jarabe/model/Makefile.am
@@ -19,4 +19,5 @@ sugar_PYTHON = \
session.py \
sound.py \
processmanagement.py \
+ feedback_collector.py \
virtualkeyboard.py
diff --git a/src/jarabe/model/feedback_collector.py b/src/jarabe/model/feedback_collector.py
new file mode 100644
index 0000000..c0deae2
--- /dev/null
+++ b/src/jarabe/model/feedback_collector.py
@@ -0,0 +1,201 @@
+# Copyright (C) 2011, Aleksey Lim
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+import os
+import time
+import httplib
+import logging
+import tarfile
+import threading
+from cStringIO import StringIO
+from os.path import join, exists, basename
+from email.mime.multipart import MIMEMultipart
+from email.mime.application import MIMEApplication
+from email.generator import Generator
+from email.encoders import encode_noop
+
+import gconf
+import gobject
+import simplejson
+
+from sugar import logger, feedback, util
+
+
+_reports = {}
+_logs = set()
+_host = None
+_port = None
+
+
+def start(host, port, auto_submit_delay):
+ global _host
+ global _port
+
+ _host = host
+ _port = port
+
+ if auto_submit_delay > 0:
+ gobject.timeout_add_seconds(auto_submit_delay, _submit)
+
+
+def update(bundle_id, report, log_file):
+ if bundle_id not in _reports:
+ _reports[bundle_id] = {}
+ stat = _reports[bundle_id]
+
+ for key, count in report.items():
+ if key not in stat:
+ stat[key] = 0
+ stat[key] += count
+
+ if log_file:
+ _logs.add(log_file)
+
+
+def is_empty():
+ report, shell_log = feedback.flush()
+ if report:
+ if shell_log:
+ shell_log = join(logger.get_logs_dir(), 'shell.log')
+ update('shell', report, shell_log)
+
+ return not _reports
+
+
+def submit(message):
+ from jarabe.journal import misc
+
+ client = gconf.client_get_default()
+ jabber = client.get_string('/desktop/sugar/collaboration/jabber_server')
+ nick = client.get_string("/desktop/sugar/user/nick")
+
+ data = {'message': message,
+ 'serial_number': misc.get_xo_serial(),
+ 'nick': nick,
+ 'jabber_server': jabber,
+ }
+ _submit(data)
+
+
+def anonymous_submit():
+ _submit()
+
+
+def _submit(data=None):
+ if data:
+ _reports.update(data)
+ if is_empty():
+ return True
+
+ logging.debug('Sending feedback report: %r', _reports)
+
+ report = simplejson.dumps(_reports)
+ _reports.clear()
+
+ tar_file = util.TempFilePath()
+ tar = tarfile.open(tar_file, 'w:gz')
+
+ while _logs:
+ log_file = _logs.pop()
+ if exists(log_file):
+ tar.add(log_file, arcname=basename(log_file))
+
+ report_file = tarfile.TarInfo('report')
+ report_file.mode = 0644
+ report_file.mtime = int(time.time())
+ report_file.size = len(report)
+ tar.addfile(report_file, StringIO(report))
+
+ tar.close()
+
+ _SubmitThread(tar_file).run()
+
+ return True
+
+
+class _SubmitThread(threading.Thread):
+
+ def __init__(self, tar_file):
+ threading.Thread.__init__(self)
+ self._tar_file = tar_file
+
+ def run(self):
+ try:
+ message = _FormData()
+ attachment = MIMEApplication(file(self._tar_file).read(),
+ _encoder=encode_noop)
+ message.attach_file(attachment,
+ name='report', filename='report.tar.gz')
+ body, headers = message.get_request_data()
+
+ conn = httplib.HTTPSConnection(_host, _port)
+ conn.request('POST', '/', body, headers)
+ response = conn.getresponse()
+
+ if response.status != 200:
+ logging.error('Incorrect feedback submit: %s, %s',
+ response.status, response.read())
+
+ except Exception:
+ logging.exception('Cannot submit feedback')
+ finally:
+ os.unlink(self._tar_file)
+ self._tar_file = None
+
+
+class _FormData(MIMEMultipart):
+ '''A simple RFC2388 multipart/form-data implementation.
+
+ A snippet from http://bugs.python.org/issue3244
+
+ '''
+
+ def __init__(self, boundary=None, _subparts=None, **kwargs):
+ MIMEMultipart.__init__(self, _subtype='form-data',
+ boundary=boundary, _subparts=_subparts, **kwargs)
+
+ def attach(self, subpart):
+ if 'MIME-Version' in subpart:
+ if subpart['MIME-Version'] != self['MIME-Version']:
+ raise ValueError('subpart has incompatible MIME-Version')
+ # Note: This isn't strictly necessary, but there is no point in
+ # including a MIME-Version header in each subpart.
+ del subpart['MIME-Version']
+ MIMEMultipart.attach(self, subpart)
+
+ def attach_file(self, subpart, name, filename):
+ '''
+ Attach a subpart, setting it's Content-Disposition header to "file".
+ '''
+ name = name.replace('"', '\\"')
+ filename = filename.replace('"', '\\"')
+ subpart['Content-Disposition'] = \
+ 'form-data; name="%s"; filename="%s"' % (name, filename)
+ self.attach(subpart)
+
+ def get_request_data(self, trailing_newline=True):
+ '''Return the encoded message body.'''
+ f = StringIO()
+ generator = Generator(f, mangle_from_=False)
+ # pylint: disable-msg=W0212
+ generator._dispatch(self)
+ # HTTP needs a trailing newline. Since our return value is likely to
+ # be passed directly to an HTTP connection, we might as well add it
+ # here.
+ if trailing_newline:
+ f.write('\n')
+ body = f.getvalue()
+ headers = dict(self)
+ return body, headers
diff --git a/src/jarabe/model/shell.py b/src/jarabe/model/shell.py
index 69b45e2..b9cb0c2 100644
--- a/src/jarabe/model/shell.py
+++ b/src/jarabe/model/shell.py
@@ -23,12 +23,14 @@ import wnck
import gobject
import gtk
import dbus
+import simplejson
from sugar import wm
from sugar import dispatch
from sugar.graphics.xocolor import XoColor
from sugar.presence import presenceservice
+from jarabe.model import feedback_collector
from jarabe.model.bundleregistry import get_registry
_SERVICE_NAME = "org.laptop.Activity"
@@ -220,6 +222,12 @@ class Activity(gobject.GObject):
else:
return self._activity_info.get_path()
+ def get_bundle_id(self):
+ if self._activity_info is None:
+ return None
+ else:
+ return self._activity_info.get_bundle_id()
+
def get_activity_name(self):
"""Returns the activity's bundle name"""
if self._activity_info is None:
@@ -618,6 +626,14 @@ class ShellModel(gobject.GObject):
logging.error('Model for activity id %s does not exist.',
activity_id)
+ def notify_feedback(self, activity_id, report, log_file):
+ home_activity = self.get_activity_by_id(activity_id)
+ if home_activity is not None:
+ feedback_collector.update(home_activity.get_bundle_id(),
+ simplejson.loads(report), log_file)
+ else:
+ logging.error('No %s activity for sending feedback', activity_id)
+
def _check_activity_launched(self, activity_id):
home_activity = self.get_activity_by_id(activity_id)
diff --git a/src/jarabe/view/service.py b/src/jarabe/view/service.py
index bb71694..a576eb9 100644
--- a/src/jarabe/view/service.py
+++ b/src/jarabe/view/service.py
@@ -97,6 +97,11 @@ class UIService(dbus.service.Object):
def NotifyLaunchFailure(self, activity_id):
shell.get_model().notify_launch_failed(activity_id)
+ @dbus.service.method(_DBUS_SHELL_IFACE,
+ in_signature="sss", out_signature="")
+ def Feedback(self, activity_id, report, log_file):
+ shell.get_model().notify_feedback(activity_id, report, log_file)
+
@dbus.service.signal(_DBUS_OWNER_IFACE, signature="s")
def ColorChanged(self, color):
pass
--
1.7.3.4
More information about the Dextrose
mailing list