Attachments you submit will be routed for moderation. If you have an account, please log in first.

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, 4 years ago)

Initial patch

  • 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  
    5959extensions/cpsection/power/Makefile 
    6060extensions/cpsection/updater/backends/Makefile 
    6161extensions/cpsection/updater/Makefile 
     62extensions/cpsection/report/Makefile 
    6263extensions/deviceicon/Makefile 
    6364extensions/globalkey/Makefile 
    6465extensions/Makefile 
  • data/icons/Makefile.am

    diff --git a/data/icons/Makefile.am b/data/icons/Makefile.am
    index d2f4ede..8e2f14a 100644
    a b  
    99        module-language.svg             \ 
    1010        module-network.svg              \ 
    1111        module-power.svg                \ 
    12         module-updater.svg 
     12        module-updater.svg              \ 
     13        module-report.svg 
    1314 
    1415EXTRA_DIST = $(sugar_DATA) 
  • (a) /dev/null vs. (b) b/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
    a b  
     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  
    134134    </schema> 
    135135 
    136136    <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> 
    137149      <key>/schemas/desktop/sugar/power/automatic</key> 
    138150      <applyto>/desktop/sugar/power/automatic</applyto> 
    139151      <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 
     1SUBDIRS = aboutme aboutcomputer datetime frame keyboard language network power updater report 
    22 
    33sugardir = $(pkgdatadir)/extensions/cpsection 
    44sugar_PYTHON = __init__.py 
  • (a) /dev/null vs. (b) b/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
    a b  
     1sugardir = $(pkgdatadir)/extensions/cpsection/report 
     2 
     3sugar_PYTHON =          \ 
     4        __init__.py     \ 
     5        logcollect.py   \ 
     6        model.py        \ 
     7        view.py          
  • (a) /dev/null vs. (b) b/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
    a b  
     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 
     17from gettext import gettext as _ 
     18 
     19CLASS = 'ProblemReport' 
     20ICON = 'module-report' 
     21TITLE = _('Report a problem') 
     22 
     23 
  • (a) /dev/null vs. (b) b/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
    a b  
     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 
     43import os 
     44import zipfile 
     45import glob 
     46import sys 
     47import time 
     48import subprocess 
     49import logging 
     50 
     51# The next couple are used by LogSend 
     52import httplib 
     53import mimetypes 
     54import urlparse 
     55 
     56# Sugar info 
     57import jarabe.config 
     58import sugar.presence.presenceservice 
     59 
     60_logger = logging.getLogger('LogCollect') 
     61 
     62class 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 
     281class 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 
     420class 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 
  • (a) /dev/null vs. (b) b/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
    a b  
     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 
     18from gettext import gettext as _ 
     19 
     20import os 
     21import logging 
     22import logcollect 
     23import gconf 
     24 
     25from sugar.datastore import datastore 
     26 
     27_logger = logging.getLogger('LogCollect') 
     28 
     29def 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 
     44def 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 
  • (a) /dev/null vs. (b) b/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
    a b  
     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 
     17import gtk 
     18from gettext import gettext as _ 
     19 
     20from sugar.graphics import style 
     21 
     22from jarabe.controlpanel.sectionview import SectionView 
     23from jarabe.controlpanel.inlinealert import InlineAlert 
     24 
     25import logcollect 
     26 
     27class 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.'))