Ticket #1439: 0001-Option-2-Remove-Upload-logs-button-entirely.patch

File 0001-Option-2-Remove-Upload-logs-button-entirely.patch, 22.2 KB (added by wadeb, 15 years ago)

Option 2 for Log activity: Remove Upload logs button entirely.

  • deleted file logcollect.py

    From 3401508ba80b84078ef838807c534ba1830bec56 Mon Sep 17 00:00:00 2001
    From: Wade Brainerd <wadetb@gmail.com>
    Date: Sun, 27 Sep 2009 21:17:26 -0400
    Subject: [PATCH] Option 2: Remove Upload logs button entirely.
    
    ---
     logcollect.py |  554 ---------------------------------------------------------
     logviewer.py  |   64 -------
     2 files changed, 0 insertions(+), 618 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..63ca22d 100644
    a b from sugar.graphics.toolbutton import ToolButton 
    3333from sugar.graphics.toggletoolbutton import ToggleToolButton
    3434from sugar.graphics.palette import Palette
    3535from sugar.graphics.alert import NotifyAlert
    36 from logcollect import LogCollect, LogSend
    3736from sugar.graphics.toolbarbox import ToolbarButton, ToolbarBox
    3837from sugar.activity.widgets import *
    3938
    class LogActivity(activity.Activity): 
    399398        delete_btn.connect('clicked', self._delete_log_cb)
    400399        edit_toolbar.insert(delete_btn, -1)
    401400
    402         self.collector_palette = CollectorPalette(self)
    403         collector_btn = ToolButton('zoom-best-fit')
    404         collector_btn.set_palette(self.collector_palette)
    405         collector_btn.connect('clicked', self._logviewer_cb)
    406         edit_toolbar.insert(collector_btn, -1)
    407 
    408401        edit_toolbar.show_all()
    409402
    410403        edit_button = ToolbarButton()
    class LogActivity(activity.Activity): 
    471464                    { 'error': err.strerror, 'file': logfile }
    472465                notify.connect('response', _notify_response_cb, self)
    473466                self.add_alert(notify)
    474 
    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()
    487        
    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