Ticket #1439: 0001-New-control-panel-Report-a-problem.patch
File 0001-New-control-panel-Report-a-problem.patch, 38.6 KB (added by wadeb, 15 years ago) |
---|
-
configure.ac
From ac617f8585b4ef457e5860aadfb162c563deef3a Mon Sep 17 00:00:00 2001 From: Wade Brainerd <wadetb@gmail.com> Date: Fri, 16 Oct 2009 22:20:06 -0400 Subject: [PATCH] New control panel: "Report a problem" Gathers system and Sugar logs, system information, and user notes. Zips them all up and uploads them to a central server. See http://wiki.sugarlabs.org/go/Features/Problem_Reports --- configure.ac | 1 + data/icons/Makefile.am | 3 +- data/icons/module-report.svg | 140 ++++++++ data/sugar.schemas.in | 12 + extensions/cpsection/Makefile.am | 2 +- extensions/cpsection/report/Makefile.am | 7 + extensions/cpsection/report/__init__.py | 23 ++ extensions/cpsection/report/logcollect.py | 506 +++++++++++++++++++++++++++++ extensions/cpsection/report/model.py | 72 ++++ extensions/cpsection/report/view.py | 95 ++++++ 10 files changed, 859 insertions(+), 2 deletions(-) create mode 100644 data/icons/module-report.svg create mode 100644 extensions/cpsection/report/Makefile.am create mode 100644 extensions/cpsection/report/__init__.py create mode 100644 extensions/cpsection/report/logcollect.py create mode 100644 extensions/cpsection/report/model.py create mode 100644 extensions/cpsection/report/view.py diff --git a/configure.ac b/configure.ac index fe3e6eb..11e398c 100644
a b extensions/cpsection/network/Makefile 59 59 extensions/cpsection/power/Makefile 60 60 extensions/cpsection/updater/backends/Makefile 61 61 extensions/cpsection/updater/Makefile 62 extensions/cpsection/report/Makefile 62 63 extensions/deviceicon/Makefile 63 64 extensions/globalkey/Makefile 64 65 extensions/Makefile -
data/icons/Makefile.am
diff --git a/data/icons/Makefile.am b/data/icons/Makefile.am index d2f4ede..8e2f14a 100644
a b sugar_DATA = \ 9 9 module-language.svg \ 10 10 module-network.svg \ 11 11 module-power.svg \ 12 module-updater.svg 12 module-updater.svg \ 13 module-report.svg 13 14 14 15 EXTRA_DIST = $(sugar_DATA) -
new file data/icons/module-report.svg
diff --git a/data/icons/module-report.svg b/data/icons/module-report.svg new file mode 100644 index 0000000..b6eb479
- + 1 <?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ 2 <!ENTITY stroke_color "#000"> 3 <!ENTITY fill_color "#fff"> 4 ]> 5 <!-- Created with Inkscape (http://www.inkscape.org/) --> 6 7 <svg 8 xmlns:svg="http://www.w3.org/2000/svg" 9 xmlns="http://www.w3.org/2000/svg" 10 version="1.0" 11 width="55" 12 height="55" 13 id="svg2"> 14 <defs 15 id="defs4" /> 16 <g 17 transform="translate(7.5,4)" 18 id="g111"> 19 <g 20 id="g113"> 21 <g 22 transform="matrix(1.7665356,0,0,1.7665356,-26.206699,-23.703877)" 23 id="g3188" 24 style="stroke:&fill_color;"> 25 <path 26 d="m 27.377856,20.343519 c 0.962106,-1.112699 2.665207,-2.669397 4.202937,-1.727197 1.292449,0.502803 2.775585,1.6621 1.957512,3.201625 -0.182463,0.554447 -0.432963,1.088431 -0.549694,1.663075" 27 id="path3252" 28 style="fill:none;stroke:&fill_color;;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> 29 <path 30 d="m 31.312226,33.70644 c -1.808083,3.055021 -4.510034,5.979882 -8.102466,6.775304 -2.013184,0.487828 -4.263446,-0.569022 -5.04412,-2.515946 -1.345509,-3.223928 -0.393273,-6.90514 1.067536,-9.918324 1.73681,-3.382313 4.485564,-6.630023 8.268217,-7.697555 1.971076,-0.597449 4.330009,0.204289 5.255634,2.110869 1.485139,2.991757 0.708574,6.557327 -0.549181,9.486277 -0.266768,0.601928 -0.566447,1.189201 -0.89562,1.759375 z" 31 id="path3197" 32 style="fill:none;stroke:&fill_color;;stroke-width:1.51091182;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> 33 <path 34 d="m 30.640788,20.96543 c -1.100558,1.983085 -2.202454,3.965429 -3.301174,5.949533" 35 id="path3199" 36 style="fill:none;stroke:&fill_color;;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> 37 <path 38 d="m 23.640267,22.485322 c 0.887247,1.787445 2.309517,3.236439 3.831245,4.497123 1.895179,0.603607 3.902778,1.265885 5.914011,1.031489" 39 id="path3201" 40 style="fill:none;stroke:&fill_color;;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" /> 41 <path 42 d="m 22.8141,25.484531 c 0.801557,0.826125 1.598557,1.656779 2.406938,2.476234" 43 id="path3203" 44 style="fill:none;stroke:&fill_color;;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> 45 <path 46 d="m 21.785315,26.831706 c 0.8524,0.8851 1.711393,1.7641 2.551072,2.661318" 47 id="path3207" 48 style="fill:none;stroke:&fill_color;;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> 49 <path 50 d="m 20.834693,28.29192 c 0.874972,0.908554 1.756543,1.811038 2.617044,2.733363" 51 id="path3209" 52 style="fill:none;stroke:&fill_color;;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> 53 <path 54 d="m 20.016015,29.896225 c 0.852399,0.8851 1.711392,1.7641 2.551071,2.661318" 55 id="path3211" 56 style="fill:none;stroke:&fill_color;;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> 57 <path 58 d="m 19.370877,31.63468 c 0.77331,0.815057 1.576967,1.605019 2.311559,2.455123" 59 id="path3213" 60 style="fill:none;stroke:&fill_color;;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> 61 <path 62 d="m 19.111902,33.697954 c 0.513893,0.585955 1.350409,1.527516 1.685883,1.92411" 63 id="path3215" 64 style="fill:none;stroke:&fill_color;;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> 65 <path 66 d="m 19.133652,36.330772 c 0.267093,0.267429 0.533177,0.536576 0.779483,0.823549" 67 id="path3217" 68 style="fill:none;stroke:&fill_color;;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> 69 <path 70 d="M 30.786085,30.087159 C 29.669861,29.806053 28.551993,29.531158 27.438134,29.240807" 71 id="path3230" 72 style="fill:none;stroke:&fill_color;;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> 73 <path 74 d="M 30.133789,31.6517 C 28.94107,31.35605 27.750337,31.05164 26.553484,30.773066" 75 id="path3232" 76 style="fill:none;stroke:&fill_color;;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> 77 <path 78 d="M 29.344518,33.20507 C 28.120201,32.9016 26.897842,32.589379 25.668834,32.305325" 79 id="path3234" 80 style="fill:none;stroke:&fill_color;;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> 81 <path 82 d="m 28.364488,34.716219 c -1.192719,-0.29565 -2.383452,-0.60006 -3.580305,-0.878634" 83 id="path3236" 84 style="fill:none;stroke:&fill_color;;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> 85 <path 86 d="M 27.18151,36.144152 C 26.088995,35.881974 25.00304,35.580967 23.899532,35.369845" 87 id="path3238" 88 style="fill:none;stroke:&fill_color;;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> 89 <path 90 d="m 25.524151,37.400068 c -0.764399,-0.152067 -1.998073,-0.40573 -2.50927,-0.497963" 91 id="path3240" 92 style="fill:none;stroke:&fill_color;;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> 93 <path 94 d="m 23.233189,38.697641 c -0.365147,-0.09759 -0.731278,-0.193457 -1.102957,-0.263278" 95 id="path3242" 96 style="fill:none;stroke:&fill_color;;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> 97 <path 98 d="m 38.499043,20.363658 c -1.017952,1.881865 -4.079717,1.462776 -4.758813,-0.494479 -0.756214,-1.610078 0.700774,-3.591894 2.455951,-3.402434 1.878969,0.03791 3.303331,2.217955 2.302862,3.896913 z" 99 id="path3244" 100 style="fill:none;stroke:&fill_color;;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> 101 <path 102 d="m 36.397626,17.843099 c -0.91168,1.292999 -2.210038,-1.183985 -0.578475,-1.094073 0.497596,0.05601 0.846524,0.647517 0.578475,1.094073 z" 103 id="path3246" 104 style="fill:none;stroke:&fill_color;;stroke-width:0.79813904;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> 105 <path 106 d="m 32.748117,16.754734 c -1.017951,1.881865 -4.079717,1.462776 -4.758813,-0.494479 -0.756213,-1.610078 0.700775,-3.591894 2.455952,-3.402434 1.871021,0.02637 3.307365,2.231278 2.302861,3.896913 z" 107 id="path3248" 108 style="fill:none;stroke:&fill_color;;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> 109 <path 110 d="m 30.781161,14.311805 c -0.911681,1.292999 -2.210039,-1.183985 -0.578475,-1.094073 0.497596,0.05601 0.846524,0.647517 0.578475,1.094073 z" 111 id="path3250" 112 style="fill:none;stroke:&fill_color;;stroke-width:0.79813904;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> 113 <path 114 d="M 26.988213,20.231746 C 26.282875,18.987831 25.577538,17.743915 24.8722,16.5 c -1.603332,0.1 -3.206664,0.2 -4.809996,0.3" 115 id="path3260" 116 style="fill:none;stroke:&fill_color;;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> 117 <path 118 d="m 33.361036,23.684723 c 1.528205,0.02338 3.056409,0.04676 4.584614,0.07014 0.62756,1.23389 1.255121,2.46778 1.882681,3.70167" 119 id="path3262" 120 style="fill:none;stroke:&fill_color;;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> 121 <path 122 d="m 23.851158,22.042526 c -0.365874,-1.022406 -0.731748,-2.044811 -1.097622,-3.067217 -2.721645,0.918756 -5.443289,1.837512 -8.164934,2.756268" 123 id="path3264" 124 style="fill:none;stroke:&fill_color;;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> 125 <path 126 d="m 33.777334,27.207476 c 1.158007,-0.142593 2.316013,-0.285187 3.47402,-0.42778 0.165967,2.472733 0.331935,4.945467 0.497902,7.4182" 127 id="path3266" 128 style="fill:none;stroke:&fill_color;;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> 129 <path 130 d="m 20.388485,25.668558 c -0.346432,-0.886626 -0.692865,-1.773252 -1.039297,-2.659878 -2.288056,2.168901 -4.576111,4.337803 -6.864167,6.506704" 131 id="path3268" 132 style="fill:none;stroke:&fill_color;;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> 133 <path 134 d="m 32.410841,31.851473 c 0.973352,-0.305797 1.946705,-0.611593 2.920057,-0.91739 -0.675933,3.13739 -1.351865,6.274779 -2.027798,9.412169" 135 id="path3270" 136 style="fill:none;stroke:&fill_color;;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> 137 </g> 138 </g> 139 </g> 140 </svg> -
data/sugar.schemas.in
diff --git a/data/sugar.schemas.in b/data/sugar.schemas.in index 8c43930..831e011 100644
a b 134 134 </schema> 135 135 136 136 <schema> 137 <key>/schemas/desktop/sugar/logcollect_server</key> 138 <applyto>/desktop/sugar/logcollect_server</applyto> 139 <owner>sugar</owner> 140 <type>string</type> 141 <default>logcollect.sugarlabs.org</default> 142 <locale name="C"> 143 <short>Log Collect Server</short> 144 <long>Url of the server logs are uploaded to.</long> 145 </locale> 146 </schema> 147 148 <schema> 137 149 <key>/schemas/desktop/sugar/power/automatic</key> 138 150 <applyto>/desktop/sugar/power/automatic</applyto> 139 151 <owner>sugar</owner> -
extensions/cpsection/Makefile.am
diff --git a/extensions/cpsection/Makefile.am b/extensions/cpsection/Makefile.am index dd0a6b8..f20968e 100644
a b 1 SUBDIRS = aboutme aboutcomputer datetime frame keyboard language network power updater 1 SUBDIRS = aboutme aboutcomputer datetime frame keyboard language network power updater report 2 2 3 3 sugardir = $(pkgdatadir)/extensions/cpsection 4 4 sugar_PYTHON = __init__.py -
new file extensions/cpsection/report/Makefile.am
diff --git a/extensions/cpsection/report/Makefile.am b/extensions/cpsection/report/Makefile.am new file mode 100644 index 0000000..5c5a31a
- + 1 sugardir = $(pkgdatadir)/extensions/cpsection/report 2 3 sugar_PYTHON = \ 4 __init__.py \ 5 logcollect.py \ 6 model.py \ 7 view.py -
new file extensions/cpsection/report/__init__.py
diff --git a/extensions/cpsection/report/__init__.py b/extensions/cpsection/report/__init__.py new file mode 100644 index 0000000..24082f7
- + 1 # Copyright (C) 2008, OLPC 2 # 3 # This program is free software; you can redistribute it and/or modify 4 # it under the terms of the GNU General Public License as published by 5 # the Free Software Foundation; either version 2 of the License, or 6 # (at your option) any later version. 7 # 8 # This program is distributed in the hope that it will be useful, 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 # GNU General Public License for more details. 12 # 13 # You should have received a copy of the GNU General Public License 14 # along with this program; if not, write to the Free Software 15 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 16 17 from gettext import gettext as _ 18 19 CLASS = 'ProblemReport' 20 ICON = 'module-report' 21 TITLE = _('Report a problem') 22 23 -
new file extensions/cpsection/report/logcollect.py
diff --git a/extensions/cpsection/report/logcollect.py b/extensions/cpsection/report/logcollect.py new file mode 100644 index 0000000..5820571
- + 1 # Copyright (C) 2007, Pascal Scheffers <pascal@scheffers.net> 2 # 3 # Permission is hereby granted, free of charge, to any person 4 # obtaining a copy of this software and associated documentation 5 # files (the "Software"), to deal in the Software without 6 # restriction, including without limitation the rights to use, 7 # copy, modify, merge, publish, distribute, sublicense, and/or sell 8 # copies of the Software, and to permit persons to whom the 9 # Software is furnished to do so, subject to the following 10 # conditions: 11 # 12 # The above copyright notice and this permission notice shall be 13 # included in all copies or substantial portions of the Software. 14 # 15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 # OTHER DEALINGS IN THE SOFTWARE. 23 # 24 # log-collect for Sugar 25 # 26 # Compile a report containing: 27 # * Basic system information: 28 # ** Serial number 29 # ** Battery type 30 # ** Build number 31 # ** Uptime 32 # ** disk free space 33 # ** ... 34 # * Installed packages list 35 # * All relevant log files (all of them, at first) 36 # 37 # The report is output as a tarfile 38 # 39 # This file has two modes: 40 # 1. It is a stand-alone python script, when invoked as 'log-collect' 41 # 2. It is a python module. 42 43 import os 44 import zipfile 45 import glob 46 import sys 47 import time 48 import subprocess 49 import logging 50 51 # The next couple are used by LogSend 52 import httplib 53 import mimetypes 54 import urlparse 55 56 # Sugar info 57 import jarabe.config 58 import sugar.presence.presenceservice 59 60 _logger = logging.getLogger('LogCollect') 61 62 class MachineProperties: 63 """Various machine properties in easy to access chunks. 64 """ 65 66 def __read_file(self, filename): 67 """Read the entire contents of a file and return it as a string""" 68 69 try: 70 f = open(filename) 71 data = f.read() 72 f.close() 73 return data 74 75 except Exception, e: 76 _logger.debug("Unable to read '%s':\n%s\n" % (filename, e)) 77 return '' 78 79 def release_id(self): 80 build_no = self.__read_file('/boot/olpc_build') 81 82 if build_no == '': 83 build_no = self.__read_file('/etc/redhat-release') 84 85 if build_no == '': 86 try: 87 popen = subprocess.Popen(['lsb_release', '-ds'], 88 stdout=subprocess.PIPE) 89 except OSError, e: 90 if e.errno != errno.ENOENT: 91 raise 92 else: 93 build_no, stderr_ = popen.communicate() 94 95 if build_no == '': 96 build_no = 'Not available' 97 98 return build_no.strip() 99 100 def uptime(self): 101 for line in self.__read_file('/proc/uptime').splitlines(): 102 if line != '': 103 return line 104 return '' 105 106 def loadavg(self): 107 for line in self.__read_file('/proc/loadavg').splitlines(): 108 if line != '': 109 return line 110 return '' 111 112 def kernel_version(self): 113 for line in self.__read_file('/proc/version').splitlines(): 114 if line != '': 115 return line 116 return '' 117 118 def memfree(self): 119 for line in self.__read_file('/proc/meminfo').splitlines(): 120 if line.find('MemFree:') > -1: 121 return line[8:].strip() 122 123 def _ofw_mfg_data(self, item): 124 """Return mfg data item from /ofw/mfg-data/""" 125 126 if not os.path.exists('/ofw/mfg-data/'+item): 127 return '' 128 129 v = self.__read_file('/ofw/mfg-data/'+item) 130 # Remove trailing 0 character, if any: 131 if v != '' and ord(v[len(v)-1]) == 0: 132 v = v[:len(v)-1] 133 134 return v 135 136 def ofw_serial_number(self): 137 return self._ofw_mfg_data('SN') 138 139 def ofw_motherboard_number(self): 140 return self._ofw_mfg_data('B#') 141 142 def ofw_board_revision(self): 143 s = self._ofw_mfg_data('SG')[0:1] 144 if s == '': 145 return '' 146 147 return '%02X' % ord(self._ofw_mfg_data('SG')[0:1]) 148 149 def ofw_uuid(self): 150 return self._ofw_mfg_data('U#') 151 152 def ofw_keyboard(self): 153 kb = self._ofw_mfg_data('KM') + '-' 154 kb += self._ofw_mfg_data('KL') + '-' 155 kb += self._ofw_mfg_data('KV') 156 return kb 157 158 def ofw_wireless_mac(self): 159 return self._ofw_mfg_data('WM') 160 161 def ofw_bios_version(self): 162 return self._ofw_mfg_data('BV') 163 164 def ofw_country(self): 165 return self._ofw_mfg_data('LA') 166 167 def ofw_localization(self): 168 return self._ofw_mfg_data('LO') 169 170 def _xo_battery_info(self, item): 171 """ from /sys/class/power-supply/olpc-battery/ """ 172 root = '/sys/class/power_supply/olpc-battery/' 173 if not os.path.exists(root+item): 174 return '' 175 176 return self.__read_file(root+item).strip() 177 178 def xo_battery_serial_number(self): 179 return self._xo_battery_info('serial_number') 180 181 def xo_battery_capacity(self): 182 return self._xo_battery_info('capacity') + ' ' + \ 183 self._xo_battery_info('capacity_level') 184 185 def xo_battery_info(self): 186 #Should be just: 187 #return self._xo_battery_info('uevent') 188 189 #But because of a bug in the kernel, that has trash, lets filter: 190 bi = '' 191 for line in self._xo_battery_info('uevent').splitlines(): 192 if line.startswith('POWER_'): 193 bi += line + '\n' 194 195 return bi 196 197 def disksize(self, path): 198 return os.statvfs(path).f_bsize * os.statvfs(path).f_blocks 199 200 def diskfree(self, path): 201 return os.statvfs(path).f_bsize * os.statvfs(path).f_bavail 202 203 def _read_popen(self, cmd): 204 p = os.popen(cmd) 205 s = '' 206 try: 207 for line in p: 208 s += line 209 finally: 210 p.close() 211 212 return s 213 214 def ifconfig(self): 215 return self._read_popen('/sbin/ifconfig') 216 217 def route_n(self): 218 return self._read_popen('/sbin/route -n') 219 220 def df_a(self): 221 return self._read_popen('/bin/df -a') 222 223 def ps_auxfwww(self): 224 return self._read_popen('/bin/ps auxfwww') 225 226 def usr_bin_free(self): 227 return self._read_popen('/usr/bin/free') 228 229 def top(self): 230 return self._read_popen('/usr/bin/top -bn2') 231 232 def activity_version(self, path): 233 try: 234 fd = open(path + '/activity/activity.info', "r") 235 lines = fd.readlines() 236 fd.close() 237 238 for line in lines: 239 if line.startswith("activity_version"): 240 return line.split('=')[1].strip() 241 242 return "Unable to find activity version in activity.info." 243 244 except Exception, e: 245 return "Unable to get activity version for %s:\n%s\n" % (path, e) 246 247 def activity_bundle_id(self, path): 248 try: 249 fd = open(path + '/activity/activity.info', "r") 250 lines = fd.readlines() 251 fd.close() 252 253 for line in lines: 254 if line.startswith("bundle_id") or line.startswith('service_name'): 255 return line.split('=')[1].strip() 256 257 return "Unable to find activity bundle id in activity.info." 258 259 except Exception, e: 260 return "Unable to get activity bundle id for %s:\n%s\n" % (path, e) 261 262 def installed_activities(self): 263 paths = [] 264 paths.extend(glob.glob(os.environ['SUGAR_PATH'] + '/activities/*.activity')) 265 paths.extend(glob.glob(os.environ['HOME'] + '/Activities/*')) 266 267 s = '' 268 for path in paths: 269 s += '\nbundle_id: %s\n' % self.activity_bundle_id(path) 270 s += 'activity_version: %s\n' % self.activity_version(path) 271 s += 'path: %s\n' % path 272 273 return s 274 275 def sugar_version(self): 276 return jarabe.config.version 277 278 def user_nick(self): 279 return sugar.presence.presenceservice.get_instance().get_owner().props.nick 280 281 class LogCollect: 282 """Collect XO logfiles and machine metadata for reporting to Sugar Labs 283 284 """ 285 def __init__(self): 286 self._mp = MachineProperties() 287 288 def write_logs(self, notes, archive='', logbytes=16384): 289 """Write a zipfile containing the tails of the logfiles and machine info of the XO 290 291 Arguments: 292 notes - User notes accompanying the log 293 294 archive - Specifies the location where to store the data 295 defaults to /dev/shm/sugar-report.zip 296 297 logbytes - Maximum number of bytes to read from each log file. 298 0 means complete logfiles, not just the tail 299 -1 means only save machine info, no logs 300 """ 301 #This function is crammed with try...except to make sure we get as much 302 #data as possible, if anything fails. 303 304 if archive=='': 305 archive = '/dev/shm/sugar-report.zip' 306 307 z = zipfile.ZipFile(archive, 'w', zipfile.ZIP_DEFLATED) 308 309 try: 310 try: 311 z.writestr('info.txt', self.system_info(notes)) 312 except Exception, e: 313 z.writestr('info.txt', 314 "logcollect: could not add info.txt: %s" % e) 315 316 if logbytes > -1: 317 # Include some log files from /var/log. 318 for fn in ['dmesg', 'messages', 'cron', 'maillog','rpmpkgs', 319 'Xorg.0.log', 'spooler']: 320 self.write_log(z, '/var/log/'+fn, 'var-log/'+fn, logbytes) 321 322 # Include all current ones from sugar/logs 323 for path in glob.glob('%s/.sugar/default/logs/*.log' % os.environ['HOME']): 324 self.write_log(z, path, 'sugar-logs/'+os.path.basename(path), 0) 325 326 # Any other misc files 327 self.write_log(z, '/etc/resolv.conf', 'etc/resolv.conf', logbytes) 328 329 except Exception, e: 330 _logger.debug('While creating zip archive: %s' % e) 331 332 z.close() 333 334 return archive 335 336 def write_log(self, z, realpath, fakepath, logbytes): 337 try: 338 if os.access(realpath, os.F_OK): 339 if logbytes == 0: 340 z.write(realpath, fakepath) 341 else: 342 z.writestr(fakepath, self.file_tail(realpath, logbytes)) 343 except Exception, e: 344 z.writestr(fakepath, "logcollect: could not add %s: %s" % (realpath, e)) 345 346 def file_tail(self, filename, tailbytes): 347 """Read the tail (end) of the file 348 349 Arguments: 350 filename The name of the file to read 351 tailbytes Number of bytes to include or 0 for entire file 352 """ 353 354 data = '' 355 356 f = open(filename) 357 try: 358 fsize = os.stat(filename).st_size 359 360 if tailbytes > 0 and fsize > tailbytes: 361 f.seek(-tailbytes, 2) 362 363 data = f.read() 364 finally: 365 f.close() 366 367 return data 368 369 def system_info(self, notes): 370 """Return a string with laptop serial, battery type, build, memory info, etc.""" 371 372 s = '' 373 try: 374 # Do not include UUID! 375 s += 'laptop-info-version: 1.1\n' 376 377 s += '\n[Notes]\n%s\n' % notes 378 379 s += '\n[System]\n' 380 s += 'sugar_version: %s\n' % self._mp.sugar_version() 381 s += 'release_id: %s\n' % self._mp.release_id() 382 s += 'user_nick: %s\n' % self._mp.user_nick() 383 s += 'clock: %f\n' % time.clock() 384 s += 'date: %s\n' % time.strftime("%a, %d %b %Y %H:%M:%S +0000", 385 time.gmtime()) 386 s += 'memfree: %s\n' % self._mp.memfree() 387 s += 'disksize: %s MB\n' % ( self._mp.disksize('/') / (1024*1024) ) 388 s += 'diskfree: %s MB\n' % ( self._mp.diskfree('/') / (1024*1024) ) 389 s += 'kernel_version: %s\n' % self._mp.kernel_version() 390 s += 'uptime: %s\n' % self._mp.uptime() 391 s += 'loadavg: %s\n' % self._mp.loadavg() 392 393 s += '\n[OFW]\n' 394 s += 'serial-number: %s\n' % self._mp.ofw_serial_number() 395 s += 'motherboard-number: %s\n' % self._mp.ofw_motherboard_number() 396 s += 'board-revision: %s\n' % self._mp.ofw_board_revision() 397 s += 'keyboard: %s\n' % self._mp.ofw_keyboard() 398 s += 'wireless_mac: %s\n' % self._mp.ofw_wireless_mac() 399 s += 'firmware: %s\n' % self._mp.ofw_bios_version() 400 s += 'country: %s\n' % self._mp.ofw_country() 401 s += 'localization: %s\n' % self._mp.ofw_localization() 402 403 s += '\n[XO Battery]\n%s\n' % self._mp.xo_battery_info() 404 405 s += '\n[Activities]\n%s\n' % self._mp.installed_activities() 406 407 s += "\n[/sbin/ifconfig]\n%s\n" % self._mp.ifconfig() 408 s += "\n[/sbin/route -n]\n%s\n" % self._mp.route_n() 409 410 s += '\n[df -a]\n%s\n' % self._mp.df_a() 411 s += '\n[ps auxwww]\n%s\n' % self._mp.ps_auxfwww() 412 s += '\n[free]\n%s\n' % self._mp.usr_bin_free() 413 s += '\n[top -bn2]\n%s\n' % self._mp.top() 414 415 except Exception, e: 416 s += '\nException while building info:\n%s\n' % e 417 418 return s 419 420 class LogSend: 421 422 # post_multipart and encode_multipart_formdata have been taken from 423 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306 424 def post_multipart(self, host, selector, fields, files): 425 """ 426 Post fields and files to an http host as multipart/form-data. 427 fields is a sequence of (name, value) elements for regular form fields. 428 files is a sequence of (name, filename, value) elements for data to be uploaded as files 429 Return the server's response page. 430 """ 431 content_type, body = self.encode_multipart_formdata(fields, files) 432 h = httplib.HTTP(host) 433 h.putrequest('POST', selector) 434 h.putheader('content-type', content_type) 435 h.putheader('content-length', str(len(body))) 436 h.putheader('Host', host) 437 h.endheaders() 438 h.send(body) 439 errcode, errmsg, headers = h.getreply() 440 return h.file.read() 441 442 def encode_multipart_formdata(self, fields, files): 443 """ 444 fields is a sequence of (name, value) elements for regular form fields. 445 files is a sequence of (name, filename, value) elements for data to be uploaded as files 446 Return (content_type, body) ready for httplib.HTTP instance 447 """ 448 BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$' 449 CRLF = '\r\n' 450 L = [] 451 for (key, value) in fields: 452 L.append('--' + BOUNDARY) 453 L.append('Content-Disposition: form-data; name="%s"' % key) 454 L.append('') 455 L.append(value) 456 for (key, filename, value) in files: 457 L.append('--' + BOUNDARY) 458 L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename)) 459 L.append('Content-Type: %s' % self.get_content_type(filename)) 460 L.append('') 461 L.append(value) 462 L.append('--' + BOUNDARY + '--') 463 L.append('') 464 body = CRLF.join(L) 465 content_type = 'multipart/form-data; boundary=%s' % BOUNDARY 466 return content_type, body 467 468 def read_file(self, filename): 469 """Read the entire contents of a file and return it as a string""" 470 471 data = '' 472 473 f = open(filename) 474 try: 475 data = f.read() 476 finally: 477 f.close() 478 479 return data 480 481 def get_content_type(self, filename): 482 return mimetypes.guess_type(filename)[0] or 'application/octet-stream' 483 484 def http_post_logs(self, url, archive, notes=''): 485 #host, selector, fields, files 486 files = ('logs', os.path.basename(archive), self.read_file(archive)), 487 488 fields = ('notes', notes), 489 urlparts = urlparse.urlsplit(url) 490 _logger.debug("Sending logs to %s" % url) 491 r = self.post_multipart(urlparts[1], urlparts[2], fields, files) 492 493 _logger.debug(r) 494 495 # Parse server response 496 result = False 497 id = None 498 499 lines = r.splitlines() 500 for line in lines: 501 if line.startswith('ID='): 502 id = line[3:] 503 elif line.startswith('OK'): 504 result = True 505 506 return result, id -
new file extensions/cpsection/report/model.py
diff --git a/extensions/cpsection/report/model.py b/extensions/cpsection/report/model.py new file mode 100644 index 0000000..04da499
- + 1 # Copyright (C) 2008 One Laptop Per Child 2 # 3 # This program is free software; you can redistribute it and/or modify 4 # it under the terms of the GNU General Public License as published by 5 # the Free Software Foundation; either version 2 of the License, or 6 # (at your option) any later version. 7 # 8 # This program is distributed in the hope that it will be useful, 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 # GNU General Public License for more details. 12 # 13 # You should have received a copy of the GNU General Public License 14 # along with this program; if not, write to the Free Software 15 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 16 # 17 18 from gettext import gettext as _ 19 20 import os 21 import logging 22 import logcollect 23 import gconf 24 25 from sugar.datastore import datastore 26 27 _logger = logging.getLogger('LogCollect') 28 29 def save_logs_to_journal(archive_name): 30 _logger.debug('Saving %s to the journal.' % archive_name) 31 32 ds = datastore.create() 33 34 ds.metadata['title'] = archive_name 35 ds.metadata['mime_type'] = 'application/zip' 36 37 ds.file_path = archive_name 38 39 datastore.write(ds, transfer_ownership=True) 40 41 ds.destroy() 42 del ds 43 44 def send_logs(notes): 45 try: 46 collector = logcollect.LogCollect() 47 archive = collector.write_logs(notes) 48 49 except Exception, e: 50 _logger.debug('Failed to collect logs:\n%s\n', e) 51 return False, 0 52 53 try: 54 client = gconf.client_get_default() 55 server = client.get_string('/desktop/sugar/logcollect_server') 56 if server[:7] != 'http://': 57 server = 'http://' + server + '/' 58 59 sender = logcollect.LogSend() 60 return sender.http_post_logs(server, archive, notes) 61 62 except Exception, e: 63 _logger.debug('Failed to upload logs:\n%s\n', e) 64 65 # When the upload fails, fall back to saving the logs to the Journal. 66 try: 67 save_logs_to_journal(archive) 68 69 except Exception, e: 70 _logger.debug('Failed to save logs to journal:\n%s\n', e) 71 72 return False, 0 -
new file extensions/cpsection/report/view.py
diff --git a/extensions/cpsection/report/view.py b/extensions/cpsection/report/view.py new file mode 100644 index 0000000..cfe8806
- + 1 # Copyright (C) 2008, OLPC 2 # 3 # This program is free software; you can redistribute it and/or modify 4 # it under the terms of the GNU General Public License as published by 5 # the Free Software Foundation; either version 2 of the License, or 6 # (at your option) any later version. 7 # 8 # This program is distributed in the hope that it will be useful, 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 # GNU General Public License for more details. 12 # 13 # You should have received a copy of the GNU General Public License 14 # along with this program; if not, write to the Free Software 15 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 16 17 import gtk 18 from gettext import gettext as _ 19 20 from sugar.graphics import style 21 22 from jarabe.controlpanel.sectionview import SectionView 23 from jarabe.controlpanel.inlinealert import InlineAlert 24 25 import logcollect 26 27 class ProblemReport(SectionView): 28 def __init__(self, model, alerts): 29 SectionView.__init__(self) 30 31 self._model = model 32 self.restart_alerts = alerts 33 34 self.set_border_width(style.DEFAULT_SPACING * 2) 35 self.set_spacing(style.DEFAULT_SPACING) 36 37 description_label = gtk.Label(_('Please describe the problem you encountered.')) 38 description_label.show() 39 40 self.description_text = gtk.TextView(gtk.TextBuffer()) 41 self.description_text.props.wrap_mode = gtk.WRAP_WORD 42 self.description_text.show() 43 #self.description_text.grab_focus() 44 45 description_scroll = gtk.ScrolledWindow() 46 description_scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) 47 description_scroll.add(self.description_text) 48 description_scroll.show() 49 50 description_frame = gtk.Frame() 51 description_frame.add(description_scroll) 52 description_frame.show() 53 54 upload_warning = gtk.Label() 55 upload_warning.set_markup(_('<b>Caution:</b> The diagnostic logs attached to the report may contain personal data.')) 56 upload_warning.show() 57 58 upload_label = gtk.Label(_('Upload report')) 59 upload_label.show() 60 61 upload_button = gtk.Button() 62 upload_button.add(upload_label) 63 upload_button.show() 64 65 upload_button.connect('clicked', self.__upload_button_clicked) 66 67 self.result_label = gtk.Label(' ') 68 self.result_label.show() 69 70 self.pack_start(description_label, expand=False) 71 self.pack_start(description_frame, expand=True) 72 self.pack_start(upload_warning, expand=False) 73 self.pack_start(upload_button, expand=False) 74 self.pack_start(self.result_label, expand=False) 75 76 self.setup() 77 78 def setup(self): 79 pass 80 81 def undo(self): 82 pass 83 84 def __upload_button_clicked(self, button): 85 self.result_label.set_text(' ') 86 87 buf = self.description_text.get_buffer() 88 description = buf.get_text(buf.get_start_iter(), buf.get_end_iter()) 89 90 result, id = self._model.send_logs(description) 91 92 if result: 93 self.result_label.set_markup(_('The report was uploaded to the server. The report ID is %s.') % id) 94 else: 95 self.result_label.set_markup(_('The report could not be uploaded to the server. A copy was saved to the Journal.'))