Ticket #1439: 0001-Option-1-not-working-Upload-logs-button-displays.patch

File 0001-Option-1-not-working-Upload-logs-button-displays.patch, 22.9 KB (added by wadeb, 15 years ago)

Option 1 for Log activity (doesn't work): Bring up control panel when the Upload logs button is clicked.

  • deleted file logcollect.py

    From ba906b7e3746bf0734b78d081868bd0f81828e30 Mon Sep 17 00:00:00 2001
    From: Wade Brainerd <wadetb@gmail.com>
    Date: Sun, 27 Sep 2009 21:14:18 -0400
    Subject: [PATCH] Option 1 (not working): Upload logs button displays Report a problem control panel.
    
    ---
     logcollect.py |  554 ---------------------------------------------------------
     logviewer.py  |   70 ++------
     2 files changed, 11 insertions(+), 613 deletions(-)
     delete mode 100644 logcollect.py
    
    diff --git a/logcollect.py b/logcollect.py
    deleted file mode 100644
    index 8713048..0000000
    + -  
    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 OLPC
    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 
    49 # The next couple are used by LogSend
    50 import httplib
    51 import mimetypes
    52 import urlparse
    53 
    54 class MachineProperties:
    55     """Various machine properties in easy to access chunks.
    56     """
    57 
    58     def __read_file(self, filename):
    59         """Read the entire contents of a file and return it as a string"""
    60 
    61         data = ''
    62 
    63         f = open(filename)
    64         try:
    65             data = f.read()
    66         finally:
    67             f.close()
    68 
    69         return data
    70 
    71     def olpc_build(self):
    72         """Buildnumber, from /etc/issue"""
    73         # Is there a better place to get the build number?
    74         if not os.path.exists('/etc/issue'):
    75             return '#/etc/issue not found'
    76 
    77         # Needed, because we want to default to the first non blank line:
    78         first_line = ''
    79 
    80         for line in self.__read_file('/etc/issue').splitlines():
    81             if line.lower().find('olpc build') > -1:
    82                 return line               
    83             if first_line == '':
    84                 first_line=line
    85 
    86         return first_line
    87 
    88     def uptime(self):
    89         for line in self.__read_file('/proc/uptime').splitlines():
    90             if line != '':
    91                 return line           
    92         return ''
    93 
    94     def loadavg(self):
    95         for line in self.__read_file('/proc/loadavg').splitlines():
    96             if line != '':
    97                 return line           
    98         return ''
    99 
    100     def kernel_version(self):
    101         for line in self.__read_file('/proc/version').splitlines():
    102             if line != '':
    103                 return line           
    104         return ''
    105 
    106     def memfree(self):
    107         line = ''
    108 
    109         for line in self.__read_file('/proc/meminfo').splitlines():
    110             if line.find('MemFree:') > -1:
    111                 return line[8:].strip()
    112 
    113     def _mfg_data(self, item):
    114         """Return mfg data item from /ofw/mfg-data/"""
    115        
    116         if not os.path.exists('/ofw/mfg-data/'+item):
    117             return ''
    118        
    119         v = self.__read_file('/ofw/mfg-data/'+item)
    120         # Remove trailing 0 character, if any:
    121         if v != '' and ord(v[len(v)-1]) == 0:
    122             v = v[:len(v)-1]
    123        
    124         return v
    125            
    126     def laptop_serial_number(self):
    127         return self._mfg_data('SN')
    128 
    129     def laptop_motherboard_number(self):
    130         return self._mfg_data('B#')
    131 
    132     def laptop_board_revision(self):
    133         s = self._mfg_data('SG')[0:1]
    134         if s == '':
    135             return ''
    136            
    137         return '%02X' % ord(self._mfg_data('SG')[0:1])
    138        
    139 
    140     def laptop_uuid(self):
    141         return self._mfg_data('U#')
    142 
    143     def laptop_keyboard(self):
    144         kb = self._mfg_data('KM') + '-'
    145         kb += self._mfg_data('KL') + '-'
    146         kb += self._mfg_data('KV')
    147         return kb
    148 
    149     def laptop_wireless_mac(self):
    150         return self._mfg_data('WM')
    151 
    152     def laptop_bios_version(self):
    153         return self._mfg_data('BV')
    154 
    155     def laptop_country(self):
    156         return self._mfg_data('LA')
    157 
    158     def laptop_localization(self):
    159         return self._mfg_data('LO')
    160    
    161     def _battery_info(self, item):
    162         """ from  /sys/class/power-supply/olpc-battery/ """
    163         root = '/sys/class/power_supply/olpc-battery/'
    164         if not os.path.exists(root+item):
    165             return ''
    166        
    167         return self.__read_file(root+item).strip()
    168 
    169     def battery_serial_number(self):
    170         return self._battery_info('serial_number')
    171    
    172     def battery_capacity(self):
    173         return self._battery_info('capacity') + ' ' + \
    174                     self._battery_info('capacity_level')
    175 
    176     def battery_info(self):
    177         #Should be just:
    178         #return self._battery_info('uevent')
    179        
    180         #But because of a bug in the kernel, that has trash, lets filter:
    181         bi = ''       
    182         for line in self._battery_info('uevent').splitlines():
    183             if line.startswith('POWER_'):
    184                 bi += line + '\n'
    185        
    186         return bi
    187        
    188     def disksize(self, path):
    189         return os.statvfs(path).f_bsize * os.statvfs(path).f_blocks
    190    
    191     def diskfree(self, path):
    192         return os.statvfs(path).f_bsize * os.statvfs(path).f_bavail
    193        
    194     def _read_popen(self, cmd):
    195         p = os.popen(cmd)
    196         s = ''
    197         try:
    198             for line in p:
    199                 s += line
    200         finally:
    201             p.close()       
    202        
    203         return s
    204    
    205     def ifconfig(self):       
    206         return self._read_popen('/sbin/ifconfig')
    207                
    208     def route_n(self):       
    209         return self._read_popen('/sbin/route -n')
    210    
    211     def df_a(self):
    212         return self._read_popen('/bin/df -a')
    213  
    214     def ps_auxfwww(self):
    215         return self._read_popen('/bin/ps auxfwww')
    216    
    217     def usr_bin_free(self):
    218         return self._read_popen('/usr/bin/free')
    219 
    220     def top(self):
    221         return self._read_popen('/usr/bin/top -bn2')
    222        
    223     def installed_activities(self):       
    224         s = ''       
    225         for path in glob.glob('/usr/share/activities/*.activity'):
    226             s += os.path.basename(path) + '\n'
    227 
    228         for path in glob.glob('/home/olpc/Activities/*'):
    229             s += '~' + os.path.basename(path) + '\n'
    230            
    231         return s
    232        
    233        
    234 
    235 class LogCollect:
    236     """Collect XO logfiles and machine metadata for reporting to OLPC
    237 
    238     """
    239     def __init__(self):
    240         self._mp = MachineProperties()
    241 
    242     def write_logs(self, archive='', logbytes=15360):
    243         """Write a zipfile containing the tails of the logfiles and machine info of the XO
    244        
    245         Arguments:
    246             archive -   Specifies the location where to store the data
    247                         defaults to /dev/shm/logs-<xo-serial>.zip
    248                        
    249             logbytes -  Maximum number of bytes to read from each log file.
    250                         0 means complete logfiles, not just the tail
    251                         -1 means only save machine info, no logs
    252         """
    253         #This function is crammed with try...except to make sure we get as much
    254         #data as possible, if anything fails.
    255        
    256         if archive=='':
    257             archive = '/dev/shm/logs.zip'
    258             try:
    259                 #With serial number is more convenient, but might fail for some
    260                 #Unknown reason...
    261                 archive = '/dev/shm/logs-%s.zip' % self._mp.laptop_serial_number()
    262             except Exception:
    263                 pass
    264            
    265         z = zipfile.ZipFile(archive, 'w', zipfile.ZIP_DEFLATED)
    266        
    267         try:           
    268             try:
    269                 z.writestr('info.txt', self.laptop_info())
    270             except Exception, e:
    271                 z.writestr('info.txt',
    272                            "logcollect: could not add info.txt: %s" % e)
    273            
    274             if logbytes > -1:           
    275                 # Include some log files from /var/log.
    276                 for fn in ['dmesg', 'messages', 'cron', 'maillog','rpmpkgs',
    277                            'Xorg.0.log', 'spooler']:
    278                     try:
    279                         if os.access('/var/log/'+fn, os.F_OK):
    280                             if logbytes == 0:
    281                                 z.write('/var/log/'+fn, 'var-log/'+fn)
    282                             else:
    283                                 z.writestr('var-log/'+fn,
    284                                            self.file_tail('/var/log/'+fn, logbytes))
    285                     except Exception, e:
    286                         z.writestr('var-log/'+fn,
    287                                    "logcollect: could not add %s: %s" % (fn, e))
    288                        
    289                 # Include all current ones from sugar/logs
    290                 for path in glob.glob('/home/olpc/.sugar/default/logs/*.log'):
    291                     try:
    292                         if os.access(path, os.F_OK):
    293                             if logbytes == 0:
    294                                 z.write(path, 'sugar-logs/'+os.path.basename(path))
    295                             else:
    296                                 z.writestr('sugar-logs/'+os.path.basename(path),
    297                                            self.file_tail(path, logbytes))
    298                     except Exception, e:
    299                         z.writestr('sugar-logs/'+fn,
    300                                    "logcollect: could not add %s: %s" % (fn, e))
    301                 try:                 
    302                     z.write('/etc/resolv.conf')
    303                 except Exception, e:
    304                     z.writestr('/etc/resolv.conf',
    305                                "logcollect: could not add resolv.conf: %s" % e)
    306                    
    307         except Exception, e:
    308             print 'While creating zip archive: %s' % e           
    309        
    310         z.close()       
    311        
    312         return archive
    313 
    314     def file_tail(self, filename, tailbytes):
    315         """Read the tail (end) of the file
    316        
    317         Arguments:
    318             filename    The name of the file to read
    319             tailbytes   Number of bytes to include or 0 for entire file
    320         """
    321 
    322         data = ''
    323 
    324         f = open(filename)
    325         try:
    326             fsize = os.stat(filename).st_size
    327            
    328             if tailbytes > 0 and fsize > tailbytes:
    329                 f.seek(-tailbytes, 2)
    330                
    331             data = f.read()
    332         finally:
    333             f.close()
    334 
    335         return data       
    336              
    337 
    338     def make_report(self, target='stdout'):
    339         """Create the report
    340 
    341         Arguments:
    342             target - where to save the logs, a path or stdout           
    343 
    344         """
    345 
    346         li = self.laptop_info()
    347         for k, v in li.iteritems():
    348             print k + ': ' +v
    349            
    350         print self._mp.battery_info()
    351 
    352     def laptop_info(self):
    353         """Return a string with laptop serial, battery type, build, memory info, etc."""
    354 
    355         s = ''       
    356         try:
    357             # Do not include UUID!
    358             s += 'laptop-info-version: 1.0\n'
    359             s += 'clock: %f\n' % time.clock()
    360             s += 'date: %s' % time.strftime("%a, %d %b %Y %H:%M:%S +0000",
    361                                             time.gmtime())
    362             s += 'memfree: %s\n' % self._mp.memfree()
    363             s += 'disksize: %s MB\n' % ( self._mp.disksize('/') / (1024*1024) )
    364             s += 'diskfree: %s MB\n' % ( self._mp.diskfree('/') / (1024*1024) )
    365             s += 'olpc_build: %s\n' % self._mp.olpc_build()
    366             s += 'kernel_version: %s\n' % self._mp.kernel_version()
    367             s += 'uptime: %s\n' % self._mp.uptime()
    368             s += 'loadavg: %s\n' % self._mp.loadavg()       
    369             s += 'serial-number: %s\n' % self._mp.laptop_serial_number()
    370             s += 'motherboard-number: %s\n' % self._mp.laptop_motherboard_number()
    371             s += 'board-revision: %s\n' %  self._mp.laptop_board_revision()
    372             s += 'keyboard: %s\n' %  self._mp.laptop_keyboard()
    373             s += 'wireless_mac: %s\n' %  self._mp.laptop_wireless_mac()
    374             s += 'firmware: %s\n' %  self._mp.laptop_bios_version()
    375             s += 'country: %s\n' % self._mp.laptop_country()
    376             s += 'localization: %s\n' % self._mp.laptop_localization()
    377                
    378             s += self._mp.battery_info()
    379            
    380             s += "\n[/sbin/ifconfig]\n%s\n" % self._mp.ifconfig()
    381             s += "\n[/sbin/route -n]\n%s\n" % self._mp.route_n()
    382            
    383             s += '\n[Installed Activities]\n%s\n' % self._mp.installed_activities()
    384    
    385             s += '\n[df -a]\n%s\n' % self._mp.df_a()
    386             s += '\n[ps auxwww]\n%s\n' % self._mp.ps_auxfwww()
    387             s += '\n[free]\n%s\n' % self._mp.usr_bin_free()
    388             s += '\n[top -bn2]\n%s\n' % self._mp.top()
    389         except Exception, e:
    390             s += '\nException while building info:\n%s\n' % e
    391        
    392         return s
    393 
    394 class LogSend:
    395    
    396     # post_multipart and encode_multipart_formdata have been taken from
    397     #  http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306
    398     def post_multipart(self, host, selector, fields, files):
    399         """
    400         Post fields and files to an http host as multipart/form-data.
    401         fields is a sequence of (name, value) elements for regular form fields.
    402         files is a sequence of (name, filename, value) elements for data to be uploaded as files
    403         Return the server's response page.       
    404         """
    405         content_type, body = self.encode_multipart_formdata(fields, files)
    406         h = httplib.HTTP(host)
    407         h.putrequest('POST', selector)
    408         h.putheader('content-type', content_type)
    409         h.putheader('content-length', str(len(body)))
    410         h.putheader('Host', host)
    411         h.endheaders()
    412         h.send(body)
    413         errcode, errmsg, headers = h.getreply()
    414         return h.file.read()
    415    
    416     def encode_multipart_formdata(self, fields, files):
    417         """
    418         fields is a sequence of (name, value) elements for regular form fields.
    419         files is a sequence of (name, filename, value) elements for data to be uploaded as files
    420         Return (content_type, body) ready for httplib.HTTP instance
    421         """
    422         BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
    423         CRLF = '\r\n'
    424         L = []
    425         for (key, value) in fields:
    426             L.append('--' + BOUNDARY)
    427             L.append('Content-Disposition: form-data; name="%s"' % key)
    428             L.append('')
    429             L.append(value)
    430         for (key, filename, value) in files:
    431             L.append('--' + BOUNDARY)
    432             L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
    433             L.append('Content-Type: %s' % self.get_content_type(filename))
    434             L.append('')
    435             L.append(value)
    436         L.append('--' + BOUNDARY + '--')
    437         L.append('')
    438         body = CRLF.join(L)
    439         content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
    440         return content_type, body
    441    
    442     def read_file(self, filename):
    443         """Read the entire contents of a file and return it as a string"""
    444 
    445         data = ''
    446 
    447         f = open(filename)
    448         try:
    449             data = f.read()
    450         finally:
    451             f.close()
    452 
    453         return data
    454 
    455     def get_content_type(self, filename):
    456         return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
    457        
    458     def http_post_logs(self, url, archive):
    459         #host, selector, fields, files
    460         files = ('logs', os.path.basename(archive), self.read_file(archive)),
    461        
    462         # Client= olpc will make the server return just "OK" or "FAIL"
    463         fields = ('client', 'xo'),
    464         urlparts = urlparse.urlsplit(url)
    465         print "Sending logs to %s" % url
    466         r = self.post_multipart(urlparts[1], urlparts[2], fields, files)
    467         print r
    468         return (r == 'OK')
    469 
    470 
    471 # This script is dual-mode, it can be used as a command line tool and as
    472 # a library.
    473 if sys.argv[0].endswith('logcollect.py') or \
    474         sys.argv[0].endswith('logcollect'):
    475     print 'log-collect utility 1.0'
    476        
    477     lc = LogCollect()
    478     ls = LogSend()
    479 
    480     logs = ''
    481     mode = 'http'
    482    
    483     if len(sys.argv)==1:
    484         print """logcollect.py - send your XO logs to OLPC
    485        
    486 Usage:
    487     logcollect.py http  - send logs to default server
    488    
    489     logcollect.py http://server.name/submit.php
    490                          - submit logs to alternative server
    491                          
    492     logcollect.py file:/media/xxxx-yyyy/mylog.zip
    493                          - save the zip file on a USB device or SD card
    494                          
    495     logcollect.py all file:/media/xxxx-yyyy/mylog.zip
    496                         - Save to zip file and include ALL logs
    497 
    498     logcollect.py none http
    499                         - Just send info.txt, but no logs via http.
    500 
    501     logcollect.py none file
    502                         - Just save info.txt in /dev/shm/logs-SN123.zip
    503 
    504     If you specify 'all' or 'none' you must specify http or file as well.
    505         """
    506         sys.exit()
    507        
    508    
    509     logbytes = 15360   
    510     if len(sys.argv)>1:
    511         mode = sys.argv[len(sys.argv)-1]
    512         if sys.argv[1] == 'all':
    513             logbytes = 0
    514         if sys.argv[1] == 'none':
    515             logbytes = -1
    516    
    517 
    518     if mode.startswith('file'):
    519         # file://
    520         logs = mode[5:]
    521  
    522     #if mode.lower().startswith('http'):
    523     #    pass
    524     #else if mode.lower().startswith('usb'):
    525     #    pass
    526     #else if mode.lower().startswith('sd'):
    527     #    pass
    528    
    529     logs = lc.write_logs(logs, logbytes)
    530     print 'Logs saved in %s' % logs
    531    
    532     sent_ok = False
    533     if len(sys.argv)>1:
    534        mode = sys.argv[len(sys.argv)-1]
    535    
    536     if mode.startswith('http'):
    537         print "Trying to send the logs using HTTP (web)"
    538         if len(mode) == 4:
    539             url = 'http://olpc.scheffers.net/olpc/submit.tcl'
    540         else:
    541             url = mode
    542            
    543         if ls.http_post_logs(url, logs):
    544             print "Logs were sent."
    545             sent_ok = True           
    546         else:
    547             print "FAILED to send logs."
    548  
    549 
    550     if sent_ok:
    551         os.remove(logs)
    552         print "Logs were sent, tempfile deleted."
    553 
    554 
  • logviewer.py

    diff --git a/logviewer.py b/logviewer.py
    index cdd77ed..2c08a03 100644
    a b  
    1616# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
    1717
    1818import os
     19import sys
    1920import logging
    2021from gettext import gettext as _
    2122
    from sugar.graphics.toolbutton import ToolButton 
    3334from sugar.graphics.toggletoolbutton import ToggleToolButton
    3435from sugar.graphics.palette import Palette
    3536from sugar.graphics.alert import NotifyAlert
    36 from logcollect import LogCollect, LogSend
    3737from sugar.graphics.toolbarbox import ToolbarButton, ToolbarBox
    3838from sugar.activity.widgets import *
     39from jarabe import config
     40from jarabe.controlpanel.gui import ControlPanel
    3941
    4042# Should be builtin to sugar.graphics.alert.NotifyAlert...
    4143def _notify_response_cb(notify, response, activity):
    class LogActivity(activity.Activity): 
    399401        delete_btn.connect('clicked', self._delete_log_cb)
    400402        edit_toolbar.insert(delete_btn, -1)
    401403
    402         self.collector_palette = CollectorPalette(self)
    403404        collector_btn = ToolButton('zoom-best-fit')
    404         collector_btn.set_palette(self.collector_palette)
    405         collector_btn.connect('clicked', self._logviewer_cb)
     405        collector_btn.set_tooltip(_('Report a problem'))
     406        collector_btn.connect('clicked', self._collector_cb)
    406407        edit_toolbar.insert(collector_btn, -1)
    407408
    408409        edit_toolbar.show_all()
    class LogActivity(activity.Activity): 
    472473                notify.connect('response', _notify_response_cb, self)
    473474                self.add_alert(notify)
    474475
    475     def _logviewer_cb(self, widget):
    476         self.collector_palette.popup(True)
    477 
    478 class CollectorPalette(Palette):
    479     _DEFAULT_SERVER = 'http://olpc.scheffers.net/olpc/submit.tcl'
    480 
    481     def __init__(self, handler):
    482         Palette.__init__(self, _('Log Collector: Send XO information'))
    483 
    484         self._handler = handler
    485        
    486         self._collector = LogCollect()
     476    def _collector_cb(self, widget):
     477        # Allows control panel extensions to load.
     478        sys.path.append(config.ext_path)
    487479       
    488         label = gtk.Label(
    489             _('Log collector sends information about the system\n'\
    490               'and running processes to a central server.  Use\n'\
    491               'this option if you want to report a problem.'))
    492        
    493         send_button = gtk.Button(_('Send information'))
    494         send_button.connect('clicked', self._on_send_button_clicked_cb)
    495 
    496         vbox = gtk.VBox(False, 5)
    497         vbox.pack_start(label)
    498         vbox.pack_start(send_button)
    499         vbox.show_all()
    500 
    501         self.set_content(vbox)
    502 
    503     def _on_send_button_clicked_cb(self, button):
    504         success = True
    505         try:
    506             data = self._collector.write_logs()
    507             sender = LogSend()
    508             success = sender.http_post_logs(self._DEFAULT_SERVER, data)
    509         except:
    510             success = False
    511 
    512         os.remove(data)
    513         self.popdown(True)
    514 
    515         title = ''
    516         msg = ''
    517         if success:
    518             title = _('Logs sent')
    519             msg = _('The logs were uploaded to the server.')
    520         else:
    521             title = _('Logs not sent')
    522             msg = _('The logs could not be uploaded to the server. '\
    523                     'Please check your network connection.')
    524 
    525         notify = NotifyAlert()
    526         notify.props.title = title
    527         notify.props.msg = msg
    528         notify.connect('response', _notify_response_cb, self._handler)
    529         self._handler.add_alert(notify)
    530 
     480        panel = ControlPanel()
     481        panel.set_transient_for(self.get_toplevel())
     482        panel.show_section_view('report')