vx8100 fixes

"Simon C" <[email protected]>
Newsgroups gmane.comp.mobile.bitpim.devel
Message-ID <000001c5c18a$ca796c50$6400a8c0@HOME>
There is a brew command to list directories one at a time. I replaced the
existing function with this new command.
The comments in the existing code look like this has been tried before
without success, so I added it to the brew protocol file but defined an
attribute to access this new way of reading the filesystem so it would not
affect anything except the 8100. I did test on the 4400 and the 6100 and it
worked OK.

This fixes the problem with too many directories causing the filebrowser to
crash.

Also fixed bogus calender entry crash and allowed underscores and hyphens in
wallpaper and ringtone names.

New code attached with diffs.

Simon



> -----Original Message-----
> From: [email protected]
> [mailto:[email protected]]On Behalf Of Simon C
> Sent: Saturday, September 24, 2005 2:29 PM
> To: Bitpim-Devel (E-mail)
> Subject: [BitPim-devel] brew protocol problem
>
>
> I've managed to reproduce a problem reported by 8100 bitpim
> users when the
> brew directory has too many files in.
> It is due to too many directories in the filesystem, files
> themselves are
> not a problem
>
> This is a protocol trace of bitpim obtaining the contents of the brew
> directory:
>
> 14:18:07.311 LG-VX8100: brew request Data - 8 bytes
> <#! p_brew.listdirectoriesrequest !#>
> 00000000 59 02 05 62 72 65 77 00                             Y..brew.
>
> 14:18:07.391 LG-VX8100: brew response Data - 263 bytes
> <#! p_brew.listdirectoriesresponse !#>
> 00000000 59 02 00 1a 00 fc 00 62 72 65 77 2f 31 30 35 31
> Y......brew/1051
> 00000010 32 00 62 72 65 77 2f 31 31 37 39 37 00 62 72 65
> 2.brew/11797.bre
> 00000020 77 2f 31 32 34 31 34 00 62 72 65 77 2f 31 32 35
> w/12414.brew/125
> 00000030 31 33 00 62 72 65 77 2f 31 33 31 33 36 00 62 72
> 13.brew/13136.br
> 00000040 65 77 2f 31 33 34 37 38 00 62 72 65 77 2f 31 33
> ew/13478.brew/13
> 00000050 39 32 31 00 62 72 65 77 2f 31 33 39 32 32 00 62
> 921.brew/13922.b
> 00000060 72 65 77 2f 31 36 34 35 32 00 62 72 65 77 2f 31
> rew/16452.brew/1
> 00000070 36 34 35 34 00 62 72 65 77 2f 31 38 31 36 39 00
> 6454.brew/18169.
> 00000080 62 72 65 77 2f 33 33 31 34 00 62 72 65 77 2f 33
> brew/3314.brew/3
> 00000090 35 35 32 00 62 72 65 77 2f 33 36 38 32 00 62 72
> 552.brew/3682.br
> 000000a0 65 77 2f 33 39 35 39 00 62 72 65 77 2f 33 39 36
> ew/3959.brew/396
> 000000b0 36 00 62 72 65 77 2f 34 39 37 32 00 62 72 65 77
> 6.brew/4972.brew
> 000000c0 2f 35 36 39 31 00 62 72 65 77 2f 38 34 35 31 00
> /5691.brew/8451.
> 000000d0 62 72 65 77 2f 38 38 33 36 00 62 72 65 77 2f 39
> brew/8836.brew/9
> 000000e0 36 33 39 00 62 72 65 77 2f 64 6f 77 6e 6c 6f 61
> 639.brew/downloa
> 000000f0 64 00 62 72 65 77 2f 65 6e 00 62 72 65 77 2f 65
> d.brew/en.brew/e
> 00000100 73 00 00 72 65 00 00                                s..re..
>
> The response looks ok, the number of directories is 0x1A
> according to the
> response, but the actual number of directories listed in the
> packet is 2
> less. Obviously the packet has overflowed. Bitpim ignores the
> packet length
> 0xFC and also read the spurious characters "re" at the end of
> the packets as
> a directory. This results in either a crash or directories
> with garbage
> names listed, trying to list the contents of these directoris
> results in a
> crash.
>
> The directories were created by downloading loads of apps
> (free demos).
>
> Has anyone seen this behavior before? Does anyone know what
> happens with
> other brew phones when the number of directory is too long to
> list in a
> single packet.
>
>
> Simon
>
>
>
> -------------------------------------------------------
> SF.Net email is sponsored by:
> Tame your development challenges with Apache's Geronimo App Server.
> Download it for free - -and be entered to win a 42" plasma tv
> or your very
> own Sony(tm)PSP.  Click here to play:
> http://sourceforge.net/geronimo.php
> _______________________________________________
> BitPim-devel mailing list
> [email protected]
> https://lists.sourceforge.net/lists/listinfo/bitpim-devel
com_lgvx8100.py (text/plain, 18.6 KB)
### BITPIM
###
### Copyright (C) 2003-2005 Roger Binns <[email protected]>
### Copyright (C) 2005 Simon Capper <[email protected]>
###
### This program is free software; you can redistribute it and/or modify
### it under the terms of the BitPim license as detailed in the LICENSE file.
###

"""Communicate with the LG VX8100 cell phone

The VX8100 is substantially similar to the VX7000 but also supports video.

"""

# standard modules
import re
import time
import cStringIO
import sha

# my modules
import common
import commport
import copy
import com_lgvx4400
import p_brew
import p_lgvx8100
import com_lgvx7000
import com_brew
import com_phone
import com_lg
import prototypes
import bpcalendar
import call_history
import sms
import memo


from prototypes import *



class Phone(com_lgvx7000.Phone):
    "Talk to the LG VX8100 cell phone"

    desc="LG-VX8100"

    protocolclass=p_lgvx8100
    serialsname='lgvx8100'

    builtinringtones= ('Low Beep Once', 'Low Beeps', 'Loud Beep Once', 'Loud Beeps', 'VZW Default Ringtone') + \
                      tuple(['Ringtone '+`n` for n in range(1,11)]) + \
                      ('No Ring',)

    ringtonelocations= (
        # type       index-file   size-file directory-to-use lowest-index-to-use maximum-entries type-major
        ( 'ringers', 'dload/my_ringtone.dat', 'dload/my_ringtonesize.dat', 'brew/16452/lk/mr', 100, 150, 1),
        )

    builtinwallpapers = () # none

    wallpaperlocations= (
        ( 'images', 'dload/image.dat', 'dload/imagesize.dat', 'brew/16452/mp', 100, 50, 0),
        )
        
    def __init__(self, logtarget, commport):
        com_lgvx4400.Phone.__init__(self, logtarget, commport)
        self.mode=self.MODENONE

    def getfundamentals(self, results):
        """Gets information fundamental to interopating with the phone and UI.

        Currently this is:

          - 'uniqueserial'     a unique serial number representing the phone
          - 'groups'           the phonebook groups
          - 'wallpaper-index'  map index numbers to names
          - 'ringtone-index'   map index numbers to ringtone names

        This method is called before we read the phonebook data or before we
        write phonebook data.
        """

        # use a hash of ESN and other stuff (being paranoid)
        self.log("Retrieving fundamental phone information")
        self.log("Phone serial number")
        results['uniqueserial']=sha.new(self.getfilecontents("nvm/$SYS.ESN")).hexdigest()
        # now read groups
        self.log("Reading group information")
        buf=prototypes.buffer(self.getfilecontents("pim/pbgroup.dat"))
        g=self.protocolclass.pbgroups()
        g.readfrombuffer(buf)
        self.logdata("Groups read", buf.getdata(), g)
        groups={}
        for i in range(len(g.groups)):
            if len(g.groups[i].name): # sometimes have zero length names
                groups[i]={'name': g.groups[i].name }
        results['groups']=groups
        self.getwallpaperindices(results)
        self.getringtoneindices(results)
        self.log("Fundamentals retrieved")
        return results

    def savegroups(self, data):
        groups=data['groups']
        keys=groups.keys()
        keys.sort()
        g=self.protocolclass.pbgroups()
        for k in keys:
            e=self.protocolclass.pbgroup()
            e.name=groups[k]['name']
            g.groups.append(e)
        buffer=prototypes.buffer()
        g.writetobuffer(buffer)
        self.logdata("New group file", buffer.getvalue(), g)
        self.writefile("pim/pbgroup.dat", buffer.getvalue())

    def getmemo(self, result):
        # read the memo file
        try:
            buf=prototypes.buffer(self.getfilecontents("sch/neomemo.dat"))
            text_memo=self.protocolclass.textmemofile()
            text_memo.readfrombuffer(buf)
            res={}
            for m in text_memo.items:
                entry=memo.MemoEntry()
                entry.text=m.text
                entry.set_date_isostr("%d%02d%02dT%02d%02d00" % ((m.memotime)))
                res[entry.id]=entry
        except com_brew.BrewNoSuchFileException:
            res={}
        result['memo']=res
        return result

    def savememo(self, result, merge):
        text_memo=self.protocolclass.textmemofile()
        memo_dict=result.get('memo', {})
        keys=memo_dict.keys()
        keys.sort()
        text_memo.itemcount=len(keys)
        for k in keys:
            entry=self.protocolclass.textmemo()
            entry.text=memo_dict[k].text
            t=time.strptime(memo_dict[k].date, '%b %d, %Y %H:%M')
            entry.memotime=(t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min)
            text_memo.items.append(entry)
        buf=prototypes.buffer()
        text_memo.writetobuffer(buf)
        self.writefile("sch/neomemo.dat", buf.getvalue())
        return result

    def getcalendar(self,result):
        res={}
        # Read exceptions file first
        try:
            buf=prototypes.buffer(self.getfilecontents("sch/newschexception.dat"))
            ex=self.protocolclass.scheduleexceptionfile()
            ex.readfrombuffer(buf)
            self.logdata("Calendar exceptions", buf.getdata(), ex)
            exceptions={}
            for i in ex.items:
                try:
                    exceptions[i.pos].append( (i.year,i.month,i.day) )
                except KeyError:
                    exceptions[i.pos]=[ (i.year,i.month,i.day) ]
        except com_brew.BrewNoSuchFileException:
            exceptions={}

        # Now read schedule
        try:
            buf=prototypes.buffer(self.getfilecontents("sch/newschedule.dat"))
            if len(buf.getdata())<3:
                # file is empty, and hence same as non-existent
                raise com_brew.BrewNoSuchFileException()
            sc=self.protocolclass.schedulefile()
            self.logdata("Calendar", buf.getdata(), sc)
            sc.readfrombuffer(buf)
            for event in sc.events:
                # the vx8100 has a bad entry when the calender is empty
                # stop processing the calender when we hit this record
                if event.pos==0: #invalid entry
                    break                   
                entry=bpcalendar.CalendarEntry()
                entry.description=event.description
                entry.start=event.start
                entry.end=event.end
                entry.vibrate=~(event.alarmindex_vibrate&0x1) # vibarate bit is inverted in phone 0=on, 1=off
                entry.repeat = self.makerepeat(event.repeat)
                min=event.alarmminutes
                hour=event.alarmhours
                if min==0x64 or hour==0x64:
                    entry.alarm=None # no alarm set
                else:
                    entry.alarm=hour*60+min
                entry.ringtone=result['ringtone-index'][event.ringtone]['name']
                entry.snoozedelay=0
                # check for exceptions and remove them
                if event.repeat[3] and exceptions.has_key(event.pos):
                    for year, month, day in exceptions[event.pos]:
                        entry.suppress_repeat_entry(year, month, day)
                res[entry.id]=entry

            assert sc.numactiveitems==len(res)
        except com_brew.BrewNoSuchFileException:
            pass # do nothing if file doesn't exist
        result['calendar']=res
        return result

    def makerepeat(self, repeat):
        # get all the variables out of the repeat tuple
        # and convert into a bpcalender RepeatEntry
        type,dow,interval,exceptions=repeat
        if type==0:
            repeat_entry=None
        else:
            repeat_entry=bpcalendar.RepeatEntry()
            if type==1: #daily
                repeat_entry.repeat_type=repeat_entry.daily
                repeat_entry.interval=interval
            elif type==5: #'monfri'
                repeat_entry.repeat_type=repeat_entry.daily
                repeat_entry.interval=0
            elif type==2: #'weekly'
                repeat_entry.repeat_type=repeat_entry.weekly
                repeat_entry.dow=dow
                repeat_entry.interval=interval
            elif type==3: #'monthly'
                repeat_entry.repeat_type=repeat_entry.monthly
                repeat_entry.interval=interval
                repeat_entry.dow=0
            elif type==6: #'monthly' #Xth Y day (e.g. 2nd friday each month)
                repeat_entry.repeat_type=repeat_entry.monthly
                repeat_entry.interval=interval #X
                repeat_entry.dow=dow #Y
            else: # =4 'yearly'
                repeat_entry.repeat_type=repeat_entry.yearly
        return repeat_entry

    def savecalendar(self, dict, merge):
        # ::TODO::
        # what will be written to the files
        eventsf=self.protocolclass.schedulefile()
        exceptionsf=self.protocolclass.scheduleexceptionfile()
        # what are we working with
        cal=dict['calendar']
        newcal={}
        #sort into start order, makes it possible to see if the calendar has changed        
        keys=[(x.start, k) for k,x in cal.items()]
        keys.sort()
        # number of entries
        eventsf.numactiveitems=len(keys)
        pos=1
        contains_alarms=False        
        # play with each entry
        for (_,k) in keys:
            # entry is what we will return to user
            entry=cal[k]
            data=self.protocolclass.scheduleevent()
            data.pos=eventsf.packetsize()
            data.description=entry.description
            data.start=entry.start
            data.end=entry.end
            self.setalarm(entry, data)
            data.ringtone=0
            for i in dict['ringtone-index']:
                if dict['ringtone-index'][i]['name']==entry.ringtone:
                    data.ringtone=i
            # check for exceptions and add them to the exceptions list
            exceptions=0
            if entry.repeat!=None:
                if data.end[:3]==entry.no_end_date:
                    data.end=(2100, 12, 31)+data.end[3:]
                for i in entry.repeat.suppressed:
                    de=self.protocolclass.scheduleexception()
                    de.pos=data.pos
                    de.day=i.date.day
                    de.month=i.date.month
                    de.year=i.date.year
                    exceptions=1
                    exceptionsf.items.append(de)
            if entry.repeat != None:
                data.repeat=(self.getrepeattype(entry, exceptions))
            else:
                data.repeat=((0,0,0,0))
            data.unknown1=0
            data.unknown2=0
            if data.alarmindex_vibrate!=1: # if alarm set
                contains_alarms=True
            # put entry in nice shiny new dict we are building
            entry=copy.copy(entry)
            newcal[data.pos]=entry
            eventsf.events.append(data)

        buf=prototypes.buffer()
        eventsf.writetobuffer(buf)
        # We check the existing calender as changes require a reboot for the alarms
        # to work properly, also no point writing the file if it is not changing
        if buf.getvalue()!=self.getfilecontents("sch/newschedule.dat"):
            self.logdata("Writing calendar", buf.getvalue(), eventsf)
            self.writefile("sch/newschedule.dat", buf.getvalue())
            if contains_alarms:
                self.log("Your phone has to be rebooted due to the calendar changing")
                dict["rebootphone"]=True
        else:
            self.log("Phone calendar unchanged, no update required")

        buf=prototypes.buffer()
        exceptionsf.writetobuffer(buf)
        self.logdata("Writing calendar exceptions", buf.getvalue(), exceptionsf)
        self.writefile("sch/newschexception.dat", buf.getvalue())

        # fix passed in dict
        dict['calendar']=newcal

        return dict

    def getrepeattype(self, entry, exceptions):
        #convert the bpcalender type into vx8100 type
        repeat_entry=bpcalendar.RepeatEntry()
        if entry.repeat.repeat_type==repeat_entry.monthly:
            dow=entry.repeat.dow
            if entry.repeat.dow==0:
                # set interval for month type 4 to start day of month, (required by vx8100)
                interval=entry.start[2]
                type=3
            else:
                interval=entry.repeat.interval
                type=6
        elif entry.repeat.repeat_type==repeat_entry.daily:
            dow=entry.repeat.dow
            interval=entry.repeat.interval
            if entry.repeat.interval==0:
                type=5
            else:
                type=1
        elif entry.repeat.repeat_type==repeat_entry.weekly:
            dow=entry.repeat.dow
            interval=entry.repeat.interval
            type=2
        elif entry.repeat.repeat_type==repeat_entry.yearly:
            # set interval to start day of month, (required by vx8100)
            interval=entry.start[2]
            # set dow to start month, (required by vx8100)
            dow=entry.start[1]
            type=4
        return (type, dow, interval, exceptions)

    def setalarm(self, entry, data):
        # vx8100 only allows certain repeat intervals, adjust to fit, it also stores an index to the interval
        if entry.alarm>=2880:
            entry.alarm=2880
            data.alarmminutes=0
            data.alarmhours=48
            data.alarmindex_vibrate=0x10
        elif entry.alarm>=1440:
            entry.alarm=1440
            data.alarmminutes=0
            data.alarmhours=24
            data.alarmindex_vibrate=0xe
        elif entry.alarm>=120:
            entry.alarm=120
            data.alarmminutes=0
            data.alarmhours=2
            data.alarmindex_vibrate=0xc
        elif entry.alarm>=60:
            entry.alarm=60
            data.alarmminutes=0
            data.alarmhours=1
            data.alarmindex_vibrate=0xa
        elif entry.alarm>=15:
            entry.alarm=15
            data.alarmminutes=15
            data.alarmhours=0
            data.alarmindex_vibrate=0x8
        elif entry.alarm>=10:
            entry.alarm=10
            data.alarmminutes=10
            data.alarmhours=0
            data.alarmindex_vibrate=0x6
        elif entry.alarm>=5:
            entry.alarm=5
            data.alarmminutes=5
            data.alarmhours=0
            data.alarmindex_vibrate=0x4
        elif entry.alarm>=0:
            entry.alarm=0
            data.alarmminutes=0
            data.alarmhours=0
            data.alarmindex_vibrate=0x2
        else: # no alarm
            data.alarmminutes=0x64
            data.alarmhours=0x64
            data.alarmindex_vibrate=1

        # set the vibrate bit
        if data.alarmindex_vibrate > 1 and entry.vibrate==0:
            data.alarmindex_vibrate+=1
        return

    my_model='VX8100'

    def getphoneinfo(self, phone_info):
        self.log('Getting Phone Info')
        try:
            s=self.getfilecontents('brew/version.txt')
            if s[:6]=='VX8100':
                phone_info.append('Model:', "VX8100")
                req=p_brew.firmwarerequest()
                res=self.sendbrewcommand(req, self.protocolclass.firmwareresponse)
                phone_info.append('Firmware Version:', res.firmware)
                s=self.getfilecontents("nvm/$SYS.ESN")[85:89]
                txt='%02X%02X%02X%02X'%(ord(s[3]), ord(s[2]), ord(s[1]), ord(s[0]))
                phone_info.append('ESN:', txt)
                txt=self.getfilecontents("nvm/nvm/nvm_cdma")[180:190]
                phone_info.append('Phone Number:', txt)
        except:
            pass
        return


parentprofile=com_lgvx7000.Profile
class Profile(parentprofile):
    protocolclass=Phone.protocolclass
    serialsname=Phone.serialsname

    BP_Calendar_Version=3
    phone_manufacturer='LG Electronics Inc'
    phone_model='VX8100'

    WALLPAPER_WIDTH=176
    WALLPAPER_HEIGHT=184
    MAX_WALLPAPER_BASENAME_LENGTH=32
    WALLPAPER_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 ._-@"
    WALLPAPER_CONVERT_FORMAT="jpg"
   
    MAX_RINGTONE_BASENAME_LENGTH=32
    RINGTONE_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 ._-@"

    # there is an origin named 'aod' - no idea what it is for except maybe
    # 'all other downloads'

    # the vx8100 supports bluetooth for connectivity to the PC, define the "bluetooth_mgd_id"
    # to enable bluetooth discovery during phone detection
    # the bluetooth address starts with LG's the three-octet OUI, all LG phone
    # addresses start with this, it provides a way to identify LG bluetooth devices
    # during phone discovery
    # OUI=Organizationally Unique Identifier
    # see http://standards.ieee.org/regauth/oui/index.shtml for more info
    bluetooth_mfg_id="001256"

    # the 8100 doesn't have seperate origins - they are all dumped in "images"
    imageorigins={}
    imageorigins.update(common.getkv(parentprofile.stockimageorigins, "images"))
    def GetImageOrigins(self):
        return self.imageorigins

    # our targets are the same for all origins
    imagetargets={}
    imagetargets.update(common.getkv(parentprofile.stockimagetargets, "wallpaper",
                                      {'width': 176, 'height': 184, 'format': "JPEG"}))
    imagetargets.update(common.getkv(parentprofile.stockimagetargets, "pictureid",
                                      {'width': 176, 'height': 184, 'format': "JPEG"}))
    imagetargets.update(common.getkv(parentprofile.stockimagetargets, "outsidelcd",
                                      {'width': 96, 'height': 80, 'format': "JPEG"}))
    imagetargets.update(common.getkv(parentprofile.stockimagetargets, "fullscreen",
                                      {'width': 176, 'height': 220, 'format': "JPEG"}))

    def GetTargetsForImageOrigin(self, origin):
        return self.imagetargets

 
    def __init__(self):
        parentprofile.__init__(self)

    _supportedsyncs=(
        ('phonebook', 'read', None),   # all phonebook reading
        ('calendar', 'read', None),    # all calendar reading
        ('wallpaper', 'read', None),   # all wallpaper reading
        ('ringtone', 'read', None),    # all ringtone reading
        ('call_history', 'read', None),# all call history list reading
        ('sms', 'read', None),         # all SMS list reading
        ('memo', 'read', None),        # all memo list reading
        ('phonebook', 'write', 'OVERWRITE'),  # only overwriting phonebook
        ('calendar', 'write', 'OVERWRITE'),   # only overwriting calendar
        ('wallpaper', 'write', 'MERGE'),      # merge and overwrite wallpaper
        ('wallpaper', 'write', 'OVERWRITE'),
        ('ringtone', 'write', 'MERGE'),       # merge and overwrite ringtone
        ('ringtone', 'write', 'OVERWRITE'),
        ('sms', 'write', 'OVERWRITE'),        # all SMS list writing
        ('memo', 'write', 'OVERWRITE'),       # all memo list writing
        )
com_brew.py (text/plain, 35.3 KB)
### BITPIM
###
### Copyright (C) 2003-2004 Roger Binns <[email protected]>
###
### This program is free software; you can redistribute it and/or modify
### it under the terms of the BitPim license as detailed in the LICENSE file.
###
### $Id: com_brew.py,v 1.45 2005/09/19 22:23:45 djpham Exp $

"""Implements the "Brew" filesystem protocol"""

import os
import p_brew
import time
import cStringIO
import com_phone
import prototypes
import common

class BrewNotSupported(Exception):
    """This phone not supported"""
    pass

class BrewCommandException(Exception):
    def __init__(self, errnum, str=None):
        if str is None:
            str="Brew Error 0x%02x" % (errnum,)
        Exception.__init__(self, str)
        self.errnum=errnum

class BrewNoMoreEntriesException(BrewCommandException):
    def __init__(self, errnum=0x1c):
        BrewCommandException.__init__(self, errnum, "No more directory entries")

class BrewNoSuchDirectoryException(BrewCommandException):
    def __init__(self, errnum=0x08):
        BrewCommandException.__init__(self, errnum, "No such directory")

class BrewNoSuchFileException(BrewCommandException):
    def __init__(self, errnum=0x06):
        BrewCommandException.__init__(self, errnum, "No such file")

class BrewBadPathnameException(BrewCommandException):
    def __init__(self, errnum=0x1a):
        BrewCommandException.__init__(self, errnum, "Bad pathname")

class BrewFileLockedException(BrewCommandException):
    def __init__(self, errnum=0x0b):
        BrewCommandException.__init__(self, errnum, "File is locked")

class BrewNameTooLongException(BrewCommandException):
    def __init__(self, errnum=0x0d):
        BrewCommandException.__init__(self, errnum, "Name is too long")

class BrewDirectoryExistsException(BrewCommandException):
    def __init__(self, errnum=0x07):
        BrewCommandException.__init__(self, errnum, "Directory already exists")


modeignoreerrortypes=com_phone.modeignoreerrortypes+(BrewCommandException,common.CommsDataCorruption)

class DebugBrewProtocol:
    """ Emulate a phone file system using a local file system.  This is used
    when you may not have access to a physical phone, but have a copy of its
    file system.
    """
    _fs_path=''
    def __init__(self):
        pass
    def getfirmwareinformation(self):
        self.log("Getting firmware information")
    def explore0c(self):
        self.log("Trying stuff with command 0x0c")
    def offlinerequest(self, reset=False):
        self.log("Taking phone offline")
        if reset:
            self.log("Resetting phone")
    def modemmoderequest(self):
        self.log("Attempting to put phone in modem mode")
    def mkdir(self, name):
        self.log("Making directory '"+name+"'")
        os.mkdir(os.path.join(self._fs_path, name))
    def mkdirs(self, directory):
        if len(directory)<1:
            return
        dirs=directory.split('/')
        for i in range(0,len(dirs)):
            try:
                self.mkdir("/".join(dirs[:i+1]))  # basically mkdir -p
            except:
                pass
    def rmdir(self,name):
        self.log("Deleting directory '"+name+"'")
        os.rmdir(os.path.join(self._fs_path, name))
    def rmfile(self,name):
        self.log("Deleting file '"+name+"'")
        os.remove(os.path.join(self._fs_path, name))
    def rmdirs(self, path):
        self.progress(0,1, "Listing child files and directories")
        all=self.getfilesystem(path, 100)
        keys=all.keys()
        keys.sort()
        keys.reverse()
        count=0
        for k in keys:
            self.progress(count, len(keys), "Deleting "+k)
            count+=1
            if all[k]['type']=='directory':
                self.rmdir(k)
            else:
                self.rmfile(k)
        self.rmdir(path)

    def getfilesystem(self, dir="", recurse=0):
        results={}
        self.log("Listing dir '"+dir+"'")
        _pwd=os.path.join(self._fs_path, dir)
        for _root,_dir,_file in os.walk(_pwd):
            break
        for f in _file:
            _stat=os.stat(os.path.join(_pwd, f))
            _date=_stat[8]
            _name=dir+'/'+f
            results[_name]={ 'name': _name, 'type': 'file', 'size': _stat[6],
                             'date': (_date,
                                      time.strftime("%x %X", time.gmtime(_date))) }
        for d in _dir:
            results[d]={ 'name': d, 'type': 'directory' }
            if recurse>0:
                results.update(self.getfilesystem(os.path.join(dir, d),
                                                  recurse-1))
        return results

    def statfile(self, name):
        _stat=os.stat(os.path.join(self._fs_path, name))
        _date=_stat[8]
        results={ 'name': name, 'type': 'file', 'size': _stat[6],
                  'date': (_date,
                           time.strftime("%x %X", time.gmtime(_date))) }
        return results

    def writefile(self, name, contents):
        self.log("Writing file '"+name+"' bytes "+`len(contents)`)
        file(os.path.join(self._fs_path, name), 'wb').write(contents)

    def getfilecontents(self, name, use_cache=False):
        self.log("Getting file contents '"+name+"'")
        try:
            return file(os.path.join(self._fs_path, name), 'rb').read()
        except:
            raise BrewNoSuchFileException

    def _setmodebrew(self):
        self.log('_setmodebrew: in mode BREW')
        return True
    def sendbrewcommand(self, request, responseclass, callsetmode=True):
        raise NotImplemetedError
    def log(self, s):
        print s
    def logdata(self, s, data, klass=None):
        print s

class RealBrewProtocol:
    "Talk to a phone using the 'brew' protocol"

    MODEBREW="modebrew"
    brewterminator="\x7e"

    # phone uses Jan 1, 1980 as epoch.  Python uses Jan 1, 1970.  This is difference
    # plus a fudge factor of 4 days, 17 hours for no reason I can find
    _brewepochtounix=315532800+406800

    def __init__(self):
        pass
##        phone_path=os.environ.get('PHONE_FS', None)
##        if phone_path:
##            print 'Debug Phone File System:',phone_path
##            DebugBrewProtocol._fs_path=os.path.normpath(phone_path)
##            if self.__class__==BrewProtocol:
##                self.__class__=DebugBrewProtocol
##            else:
##                self._update_base_class(self.__class__)

##    def _update_base_class(self, klass):
##        _bases=[]
##        found=False
##        for e in klass.__bases__:
##            if e==BrewProtocol:
##                _bases.append(DebugBrewProtocol)
##                found=True
##            else:
##                _bases.append(e)
##        if found:
##            klass.__bases__=tuple(_bases)
##        else:
##            for e in _bases:
##                self._update_base_class(e)

    def getfirmwareinformation(self):
        self.log("Getting firmware information")
        req=p_brew.firmwarerequest()
        res=self.sendbrewcommand(req, p_brew.firmwareresponse)

    def explore0c(self):
        self.log("Trying stuff with command 0x0c")
        req=p_brew.testing0crequest()
        res=self.sendbrewcommand(req, p_brew.testing0cresponse)

    def offlinerequest(self, reset=False):
        req=p_brew.setmoderequest()
        req.request=1
        self.log("Taking phone offline")
        self.sendbrewcommand(req, p_brew.setmoderesponse)
        if reset:
            req=p_brew.setmoderequest()
            req.request=2
            self.log("Resetting phone")
            self.sendbrewcommand(req, p_brew.setmoderesponse)
            
    def modemmoderequest(self):
        # Perhaps we should modify sendbrewcommand to have an option to
        # not be picky about response.
        self.log("Attempting to put phone in modem mode")
        req=p_brew.setmodemmoderequest()
        buffer=prototypes.buffer()
        req.writetobuffer(buffer)
        data=buffer.getvalue()
        self.logdata("brew request", data, req)
        data=common.pppescape(data+common.crcs(data))+common.pppterminator
        self.comm.write(data)
        # Response could be text or a packet
        self.comm.readsome(numchars=5)
        self.mode=self.MODENONE # Probably should add a modem mode

    def mkdir(self, name):
        self.log("Making directory '"+name+"'")
	req=p_brew.mkdirrequest()
	req.dirname=name
        self.sendbrewcommand(req, p_brew.mkdirresponse)

    def mkdirs(self, directory):
        if len(directory)<1:
            return
        dirs=directory.split('/')
        for i in range(0,len(dirs)):
            try:
                self.mkdir("/".join(dirs[:i+1]))  # basically mkdir -p
            except:
                pass


    def rmdir(self,name):
        self.log("Deleting directory '"+name+"'")
	req=p_brew.rmdirrequest()
	req.dirname=name
        self.sendbrewcommand(req, p_brew.rmdirresponse)

    def rmfile(self,name):
        self.log("Deleting file '"+name+"'")
	req=p_brew.rmfilerequest()
	req.filename=name
        self.sendbrewcommand(req, p_brew.rmfileresponse)
        file_cache.clear(name)

    def rmdirs(self, path):
        self.progress(0,1, "Listing child files and directories")
        all=self.getfilesystem(path, 100)
        keys=all.keys()
        keys.sort()
        keys.reverse()
        count=0
        for k in keys:
            self.progress(count, len(keys), "Deleting "+k)
            count+=1
            if all[k]['type']=='directory':
                self.rmdir(k)
            else:
                self.rmfile(k)
        self.rmdir(path)

    def getfilesystem(self, dir="", recurse=0):
        if getattr(self.protocolclass, 'BREW_FILESYSTEM_TYPE', 0) == 1:
            return self.getfilesystem_type1(dir, recurse)
        results={}

        self.log("Listing dir '"+dir+"'")
        
        # self.log("file listing 0x0b command")
        for i in xrange(10000):
            try:
                req=p_brew.listfilerequest()
                req.entrynumber=i
                req.dirname=dir
                res=self.sendbrewcommand(req,p_brew.listfileresponse)
                results[res.filename]={ 'name': res.filename, 'type': 'file',
                                'size': res.size }
                if res.date==0:
                    results[res.filename]['date']=(0, "")
                else:
                    try:
                        date=res.date+self._brewepochtounix
                        results[res.filename]['date']=(date, time.strftime("%x %X", time.gmtime(date)))
                    except:
                        # invalid date - see SF bug #833517
                        results[res.filename]['date']=(0, "")
                    
            except BrewNoMoreEntriesException:
                break

        # i tried using 0x0a command to list subdirs but that fails when
        # mingled with 0x0b commands
        req=p_brew.listdirectoriesrequest()
        req.dirname=dir

        res=self.sendbrewcommand(req, p_brew.listdirectoriesresponse)
        for i in range(res.numentries):
            subdir=res.items[i].subdir
            # sometimes subdir can already include the parent directory
            f=subdir.rfind("/")
            if f>=0: subdir=subdir[f+1:]
            if len(dir):
                subdir=dir+"/"+subdir
            results[subdir]={ 'name': subdir, 'type': 'directory' }
            if recurse>0:
                results.update(self.getfilesystem(subdir, recurse-1))
        return results

    def getfilesystem_type1(self, dir="", recurse=0):
        results={}

        self.log("Listing dir '"+dir+"'")
        
        req=p_brew.listdirectoriesrequest()
        req.dirname=dir

        for i in xrange(10000):
            try:
                req=p_brew.listdirectoryrequest()
                req.entrynumber=i
                req.dirname=dir
                res=self.sendbrewcommand(req,p_brew.listdirectoryresponse)
                # sometimes subdir can already include the parent directory
                f=res.subdir.rfind("/")
                if f>=0: subdir=res.subdir[f+1:]
                else: subdir=res.subdir
                if len(dir):
                    subdir=dir+"/"+subdir
                results[subdir]={ 'name': subdir, 'type': 'directory' }
                if recurse>0:
                    results.update(self.getfilesystem(subdir, recurse-1))
            except BrewNoMoreEntriesException:
                break

        # self.log("file listing 0x0b command")
        for i in xrange(10000):
            try:
                req=p_brew.listfilerequest()
                req.entrynumber=i
                req.dirname=dir
                res=self.sendbrewcommand(req,p_brew.listfileresponse)
                results[res.filename]={ 'name': res.filename, 'type': 'file',
                                'size': res.size }
                if res.date==0:
                    results[res.filename]['date']=(0, "")
                else:
                    try:
                        date=res.date+self._brewepochtounix
                        results[res.filename]['date']=(date, time.strftime("%x %X", time.gmtime(date)))
                    except:
                        # invalid date - see SF bug #833517
                        results[res.filename]['date']=(0, "")
                    
            except BrewNoMoreEntriesException:
                break

        return results

    def statfile(self, name):
        # return the status of the file
        try:
            self.log('stat file '+name)
            req=p_brew.statfilerequest()
            req.filename=name
            res=self.sendbrewcommand(req, p_brew.statfileresponse)
            results={ 'name': name, 'type': 'file', 'size': res.size }
            if res.date==0:
                results['date']=(0, '')
            else:
                try:
                    date=res.date+self._brewepochtounix
                    results['date']=(date, time.strftime("%x %X", time.gmtime(date)))
                except:
                    # invalid date - see SF bug #833517
                    results['date']=(0, '')
            return results
        except:
            # something happened, we don't have any info on this file
            if __debug__:
                raise
            return None

    def writefile(self, name, contents):
        start=time.time()
        self.log("Writing file '"+name+"' bytes "+`len(contents)`)
        desc="Writing "+name
	req=p_brew.writefilerequest()
	req.filesize=len(contents)
	req.data=contents[:0x100]
	req.filename=name
        self.sendbrewcommand(req, p_brew.writefileresponse)
        # do remaining blocks
        numblocks=len(contents)/0x100
        count=0
        for offset in range(0x100, len(contents), 0x100):
	    req=p_brew.writefileblockrequest()
            count+=1
            if count>=0x100: count=1
            if count % 5==0:
                self.progress(offset>>8,numblocks,desc)
	    req.blockcounter=count
	    req.thereismore=offset+0x100<len(contents)
            block=contents[offset:]
            l=min(len(block), 0x100)
            block=block[:l]
	    req.data=block
            self.sendbrewcommand(req, p_brew.writefileblockresponse)
        end=time.time()
        if end-start>3:
            self.log("Wrote "+`len(contents)`+" bytes at "+`int(len(contents)/(end-start))`+" bytes/second")


    def getfilecontents(self, file, use_cache=False):
        if use_cache:
            node=self.statfile(file)
            if node and file_cache.hit(file, node['date'][0], node['size']):
                self.log('Reading from cache: '+file)
                _data=file_cache.data(file)
                if _data:
                    return _data
                self.log('Cache file corrupted and discarded')

        start=time.time()
        self.log("Getting file contents '"+file+"'")
        desc="Reading "+file

        data=cStringIO.StringIO()

        req=p_brew.readfilerequest()
        req.filename=file
        
        res=self.sendbrewcommand(req, p_brew.readfileresponse)
        
        filesize=res.filesize
        data.write(res.data)

        counter=0
        while res.thereismore:
            counter+=1
            if counter>0xff:
                counter=0x01
            if counter%5==0:
                self.progress(data.tell(), filesize, desc)
            req=p_brew.readfileblockrequest()
            req.blockcounter=counter
            res=self.sendbrewcommand(req, p_brew.readfileblockresponse)
            data.write(res.data)

        self.progress(1,1,desc)
        
        data=data.getvalue()

        # give the download speed if we got a non-trivial amount of data
        end=time.time()
        if end-start>3:
            self.log("Read "+`filesize`+" bytes at "+`int(filesize/(end-start))`+" bytes/second")
        
        if filesize!=len(data):
            self.log("expected size "+`filesize`+"  actual "+`len(data)`)
            self.raisecommsexception("Brew file read is incorrect size", common.CommsDataCorruption)
        if use_cache and node:
            file_cache.add(file, node.get('date', [0])[0], data)
        return data

    class DirCache:
        """This is a class that lets you do various filesystem manipulations and
        it remembers the data.  Typical usage would be if you make changes to
        files (adding, removing, rewriting) and then have to keep checking if
        files exist, add sizes etc.  This class saves the hassle of rereading
        the directory every single time.  Note that it will only see changes
        you make via this class.  If you go directly to the Brew class then
        those won't be seen.
        """
        def __init__(self, target):
            "@param target: where operations should be done after recording them here"
            self.__target=target
            self.__cache={}

        def rmfile(self, filename):
            res=self.__target.rmfile(filename)
            node=self._getdirectory(brewdirname(filename))
            if node is None: # we didn't have it
                return
            del node[brewbasename(filename)]
            return res

        def stat(self, filename):
            node=self._getdirectory(brewdirname(filename), ensure=True)
            return node.get(brewbasename(filename), None)

        def readfile(self, filename):
            node=self._getdirectory(brewdirname(filename), ensure=True)
            file=node.get(brewbasename(filename), None)
            if file is None:
                raise BrewNoSuchFileException()
            # This class only populates the 'data' portion of the file obj when needed
            data=file.get('data', None)
            if data is None:
                data=self.__target.getfilecontents(filename)
                file['data']=data
            return data

        def writefile(self, filename, contents):
            res=self.__target.writefile(filename, contents)
            node=self._getdirectory(brewdirname(filename), ensure=True)
            # we can't put the right date in since we have no idea
            # what the timezone (or the time for that matter) on the
            # phone is
            stat=node.get(brewbasename(filename), {'name': filename, 'type': 'file', 'date': (0, "")})
            stat['size']=len(contents)
            stat['data']=contents
            node[brewbasename(filename)]=stat
            return res

        def _getdirectory(self, dirname, ensure=False):
            if not ensure:
                return self.__cache.get(dirname, None)
            node=self.__cache.get(dirname, None)
            if node is not None: return node
            node={}
            fs=self.__target.getfilesystem(dirname)
            for filename in fs.keys():
                node[brewbasename(filename)]=fs[filename]
            self.__cache[dirname]=node
            return node

    def _setmodebrew(self):
        req=p_brew.memoryconfigrequest()
        respc=p_brew.memoryconfigresponse
        
        for baud in 0, 38400,115200:
            if baud:
                if not self.comm.setbaudrate(baud):
                    continue
            try:
                self.sendbrewcommand(req, respc, callsetmode=False)
                return True
            except modeignoreerrortypes:
                pass

        # send AT$CDMG at various speeds
        for baud in (0, 115200, 19200, 230400):
            if baud:
                if not self.comm.setbaudrate(baud):
                    continue
            print "Baud="+`baud`

            try:
                for line in self.comm.sendatcommand("+GMM"):
                    if line.find("SPH-A700")>0:
                        raise BrewNotSupported("This phone is not supported by BitPim", self.desc)
            except modeignoreerrortypes:
                self.log("No response to AT+GMM")
            except:
                print "GMM Exception"
                self.mode=self.MODENONE
                self.comm.shouldloop=True
                raise

            try:
                self.comm.write("AT$QCDMG\r\n")
            except:
                # some issue during writing such as user pulling cable out
                self.mode=self.MODENONE
                self.comm.shouldloop=True
                raise
            try:
                # if we got OK back then it was success
                if self.comm.readsome().find("OK")>=0:
                    break
            except modeignoreerrortypes:
                self.log("No response to setting QCDMG mode")

        # verify if we are in DM mode
        for baud in 0,38400,115200:
            if baud:
                if not self.comm.setbaudrate(baud):
                    continue
            try:
                self.sendbrewcommand(req, respc, callsetmode=False)
                return True
            except modeignoreerrortypes:
                pass
        return False

    def sendbrewcommand(self, request, responseclass, callsetmode=True):
        if callsetmode:
            self.setmode(self.MODEBREW)
        buffer=prototypes.buffer()
        request.writetobuffer(buffer)
        data=buffer.getvalue()
        self.logdata("brew request", data, request)
        data=common.pppescape(data+common.crcs(data))+common.pppterminator
        firsttwo=data[:2]
        try:
            # we logged above, and below
            data=self.comm.writethenreaduntil(data, False, common.pppterminator, logreaduntilsuccess=False) 
        except modeignoreerrortypes:
            self.mode=self.MODENONE
            self.raisecommsdnaexception("manipulating the filesystem")
        self.comm.success=True
        origdata=data
        
        # sometimes there is junk at the begining, eg if the user
        # turned off the phone and back on again.  So if there is more
        # than one 7e in the escaped data we should start after the
        # second to last one
        d=data.rfind(common.pppterminator,0,-1)
        if d>=0:
            self.log("Multiple packets in data - taking last one starting at "+`d+1`)
            self.logdata("Original data", origdata, None)
            data=data[d+1:]

        # turn it back to normal
        data=common.pppunescape(data)

        # sometimes there is other crap at the begining
        d=data.find(firsttwo)
        if d>0:
            self.log("Junk at begining of packet, data at "+`d`)
            self.logdata("Original data", origdata, None)
            self.logdata("Working on data", data, None)
            data=data[d:]
        # take off crc and terminator
        crc=data[-3:-1]
        data=data[:-3]
        calccrc=common.crcs(data)
        if calccrc!=crc:
            self.logdata("Original data", origdata, None)
            self.logdata("Working on data", data, None)
            raise common.CommsDataCorruption("Brew packet failed CRC check", self.desc)
        
        # log it
        self.logdata("brew response", data, responseclass)

        if firsttwo=="Y\x0c" and data==firsttwo:
            # we are getting an echo - the modem port has been selected
            # instead of diagnostics port
            raise common.CommsWrongPort("The port you are using is echoing data back, and is not valid for Brew data.  Most likely you have selected the modem interface when you should be using the diagnostic interface.", self.desc)

        # look for errors
        if data[0]=="Y" and data[2]!="\x00":  # Y is 0x59 which is brew command prefix
                err=ord(data[2])
                if err==0x1c:
                    raise BrewNoMoreEntriesException()
                if err==0x08:
                    raise BrewNoSuchDirectoryException()
                if err==0x06:
                    raise BrewNoSuchFileException()
                if err==0x1a:
                    raise BrewBadPathnameException()
                if err==0x0b:
                    raise BrewFileLockedException()
                if err==0x0d:
                    raise BrewNameTooLongException()
                if err==0x07:
                    raise BrewDirectoryExistsException()
                raise BrewCommandException(err)
        # parse data
        buffer=prototypes.buffer(data)
        res=responseclass()
        try:
            res.readfrombuffer(buffer)
        except:
            # we had an exception so log the data even if protocol log
            # view is not available
            self.log(formatpacketerrorlog("Error decoding response", origdata, data, responseclass))
            raise
        return res

class BrewProtocol(RealBrewProtocol):
    """This is just a wrapper class that allows the manipulation between
    RealBrewProtocol and DebugBrewProtocol classes.
    """
    def __init__(self):
        # if the env var PHONE_FS is set, we're debugging!
        phone_path=os.environ.get('PHONE_FS', None)
        if __debug__ and phone_path:
            print 'Debug Phone File System:',phone_path
            # we probably need to do this only once for the whole class,
            # but what the heck!
            DebugBrewProtocol._fs_path=os.path.normpath(phone_path)
            self._update_base_class(self.__class__)

    def _update_base_class(self, klass):
        # update the RealBrewProtocol class to DebugBrewProtocol one.
        _bases=[]
        found=False
        for e in klass.__bases__:
            if e==RealBrewProtocol:
                _bases.append(DebugBrewProtocol)
                found=True
            else:
                _bases.append(e)
        if found:
            klass.__bases__=tuple(_bases)
        else:
            for e in _bases:
                self._update_base_class(e)

def formatpacketerrorlog(str, origdata, data, klass):
    # copied from guiwidgets.LogWindow.logdata
    hd=""
    if data is not None:
        hd="Data - "+`len(data)`+" bytes\n"
        if klass is not None:
            try:
                hd+="<#! %s.%s !#>\n" % (klass.__module__, klass.__name__)
            except:
                klass=klass.__class__
                hd+="<#! %s.%s !#>\n" % (klass.__module__, klass.__name__)
        hd+=common.datatohexstring(data)
    if origdata is not None:
        hd+="\nOriginal Data - "+`len(data)`+" bytes\n"+common.datatohexstring(origdata)
    return str+" "+hd

def brewbasename(str):
    "returns basename of str"
    if str.rfind("/")>0:
        return str[str.rfind("/")+1:]
    return str

def brewdirname(str):
    "returns dirname of str"
    if str.rfind("/")>0:
        return str[:str.rfind("/")]
    return str


class SPURIOUSZERO(prototypes.BaseProtogenClass):
    """This is a special class used to consume the spurious zero in some p_brew.listfileresponse

    The three bytes are formatted as follows:

       - An optional 'null' byte (this class)
       - A byte specifying how long the directory name portion is, including trailing slash
       - A byte specifying the length of the whole name
       - The bytes of the filename (which includes the full directory name)

    Fun and games ensue because files in the root directory have a zero length directory
    name, so we have some heuristics to try and distinguish if the first byte is the
    spurious zero or not
    """
    def __init__(self, *args, **kwargs):
        super(SPURIOUSZERO,self).__init__(*args, **kwargs)
        
        self._value=None
        if self._ismostderived(SPURIOUSZERO):
            self._update(args, kwargs)

    def _update(self, args, kwargs):
        super(SPURIOUSZERO, self)._update(args, kwargs)
        
        self._complainaboutunusedargs(SPURIOUSZERO, kwargs)

        if len(args):
            raise TypeError("Unexpected arguments "+`args`)

    def readfrombuffer(self, buf):
         self._bufferstartoffset=buf.getcurrentoffset()

         # there are several cases this code has to deal with
         #
         # The data is ordered like this:
         #
         # optional spurious zero (sz)
         # dirlen
         # fulllen
         # name
         #
         # These are the various possibilities.  The first two
         # are a file in the root directory (dirlen=0), with the other
         # two being a file in a subdirectory  (dirlen>0). fulllen
         # is always >0
         #
         # A:    dirlen=0 fulllen name
         # B: sz dirlen=0 fulllen name
         # C:    dirlen>0 fulllen name
         # D: sz dirlen>0 fulllen name

         while True:  # this is just used so we can break easily

             # CASE C
             if buf.peeknextbyte()!=0:
                 self._value=-1
                 break

             # CASE B
             if buf.peeknextbyte(1)==0:
                 self._value=buf.getnextbyte() # consume sz
                 break
             
             # A & D are harder to distinguish since they both consist of a zero
             # followed by non-zero.  Consequently we examine the data for
             # consistency

             all=buf.peeknextbytes(min(max(2+buf.peeknextbyte(1), 3+buf.peeknextbyte(2)), buf.howmuchmore()))

             # are the values consistent for D?
             ddirlen=ord(all[1])
             dfulllen=ord(all[2])

             if ddirlen<dfulllen and ddirlen<len(all)-3 and all[3+ddirlen-1]=='/':
                 self._value=buf.getnextbyte() # consume sz
                 break

             # case C, do nothing
             self._value=-2
             break
             
         self._bufferendoffset=buf.getcurrentoffset()

    def writetobuffer(self, buf):
        raise NotImplementedError()

    def packetsize(self):
         raise NotImplementedError()

    def getvalue(self):
        "Returns the string we are"

        if self._value is None:
            raise prototypes.ValueNotSetException()
        return self._value

file_cache=None

class EmptyFileCache(object):
    def __init__(self, bitpim_path):
        self._path=None
        self._cache_file_name=None
        self._data={ 'file_index': 0 }
        self.esn=None
    def hit(self, file_name, datetime, data_len):
        return False
    def data(self, file_name):
        return None
    def add(self, file_name, datetime, data):
        pass
    def clear(self, file_name):
        pass
    def set_path(self, bitpim_path):
        try:
            print 'setting path to',bitpim_path
            if not bitpim_path:
                raise ValueError
            # set the paths
            self.__class__=FileCache
            self._path=os.path.join(bitpim_path, 'cache')
            self._cache_file_name=os.path.join(self._path,
                                               self._cache_index_file_name)
            self._check_path()
            self._read_index()
            self._write_index()
        except:
            self.__class__=EmptyFileCache

class FileCache(object):
    _cache_index_file_name='index.idx'
    current_version=1
    def __init__(self, bitpim_path):
        self._path=os.path.join(bitpim_path, 'cache')
        self._cache_file_name=os.path.join(self._path,
                                           self._cache_index_file_name)
        self._data={ 'file_index': 0 }
        self.esn=None
        try:
            if not bitpim_path:
                raise ValueError
            self._check_path()
            self._read_index()
            self._write_index()
        except:
            # something's wrong, disable caching
            self.__class__=EmptyFileCache

    def _check_path(self):
        try:
            os.makedirs(self._path)
        except:
            pass
        if not os.path.isdir(self._path):
            raise Exception("Bad cache directory: '"+self._path+"'")

    def _read_index(self):
        self._check_path()
        d={ 'result': {} }
        try:
            common.readversionedindexfile(self._cache_file_name, d, None,
                                          self.current_version)
            self._data.update(d['result'])
        except:
            print 'failed to read cache index file'

    def _write_index(self):
        self._check_path()
        common.writeversionindexfile(self._cache_file_name, self._data,
                                     self.current_version)

    def _entry(self, file_name):
        k=self._data.get(self.esn, None)
        if k:
            return k.get(file_name, None)

    def hit(self, file_name, datetime, data_len):
        try:
            e=self._entry(file_name)
            if e:
                return e['datetime'] and e['datetime']==datetime and \
                       e['size']==data_len
            return False
        except:
            if __debug__:
                raise
            return False

    def data(self, file_name):
        try:
            e=self._entry(file_name)
            if e:
                _data=file(os.path.join(self._path, e['cache']), 'rb').read()
                if len(_data)==e['size']:
                    return _data
        except IOError:
            return None
        except:
            if __debug__:
                raise
            return None

    def add(self, file_name, datetime, data):
        try:
            if self.esn:
                e=self._entry(file_name)
                if not e:
                    # entry does not exist, create a new one
                    self._data.setdefault(self.esn, {})[file_name]={}
                    e=self._data[self.esn][file_name]
                    e['cache']='F%05d'%self._data['file_index']
                    self._data['file_index']+=1
                # entry exists, just update the data
                e['datetime']=datetime
                e['size']=len(data)
                _cache_file_name=os.path.join(self._path, e['cache'])
                try:
                    file(_cache_file_name, 'wb').write(data)
                    self._write_index()
                except IOError:
                    # failed to write to cache file, drop this entry
                    self._read_index()
        except:
            if __debug__:
                raise

    def clear(self, file_name):
        try:
            # clear this entry if it exists
            e=self._entry(file_name)
            if e:
                try:
                    # remove the cache file
                    os.remove(os.path.join(self._path, e['cache']))
                except:
                    pass
                # and remove the entry
                del self._data[self.esn][file_name]
                self._write_index()
        except:
            if __debug__:
                raise

    def set_path(self, bitpim_path):
        try:
            print 'setting path to',bitpim_path
            if not bitpim_path:
                raise ValueError
            # set the paths
            self.__class__=FileCache
            self._path=os.path.join(bitpim_path, 'cache')
            self._cache_file_name=os.path.join(self._path,
                                               self._cache_index_file_name)
            self._check_path()
            self._read_index()
            self._write_index()
        except:
            raise
            self.__class__=EmptyFileCache
com_lgvx8100.py.diff (application/octet-stream, 982 B)
Index: com_lgvx8100.py
===================================================================
RCS file: /cvsroot/bitpim/bitpim/com_lgvx8100.py,v
retrieving revision 1.7
diff -u -r1.7 com_lgvx8100.py
--- com_lgvx8100.py	24 Sep 2005 03:18:24 -0000	1.7
+++ com_lgvx8100.py	24 Sep 2005 17:39:12 -0000
@@ -431,11 +431,11 @@
     WALLPAPER_WIDTH=176
     WALLPAPER_HEIGHT=184
     MAX_WALLPAPER_BASENAME_LENGTH=32
-    WALLPAPER_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 ."
+    WALLPAPER_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 ._-@"
     WALLPAPER_CONVERT_FORMAT="jpg"
    
     MAX_RINGTONE_BASENAME_LENGTH=32
-    RINGTONE_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 ."
+    RINGTONE_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 ._-@"
 
     # there is an origin named 'aod' - no idea what it is for except maybe
     # 'all other downloads'
com_brew.py.diff (application/octet-stream, 2.7 KB)
Index: com_brew.py
===================================================================
RCS file: /cvsroot/bitpim/bitpim/com_brew.py,v
retrieving revision 1.45
diff -u -r1.45 com_brew.py
--- com_brew.py	19 Sep 2005 22:23:45 -0000	1.45
+++ com_brew.py	25 Sep 2005 03:15:50 -0000
@@ -278,8 +278,9 @@
                 self.rmfile(k)
         self.rmdir(path)
 
-
     def getfilesystem(self, dir="", recurse=0):
+        if getattr(self.protocolclass, 'BREW_FILESYSTEM_TYPE', 0) == 1:
+            return self.getfilesystem_type1(dir, recurse)
         results={}
 
         self.log("Listing dir '"+dir+"'")
@@ -324,6 +325,56 @@
                 results.update(self.getfilesystem(subdir, recurse-1))
         return results
 
+    def getfilesystem_type1(self, dir="", recurse=0):
+        results={}
+
+        self.log("Listing dir '"+dir+"'")
+        
+        req=p_brew.listdirectoriesrequest()
+        req.dirname=dir
+
+        for i in xrange(10000):
+            try:
+                req=p_brew.listdirectoryrequest()
+                req.entrynumber=i
+                req.dirname=dir
+                res=self.sendbrewcommand(req,p_brew.listdirectoryresponse)
+                # sometimes subdir can already include the parent directory
+                f=res.subdir.rfind("/")
+                if f>=0: subdir=res.subdir[f+1:]
+                else: subdir=res.subdir
+                if len(dir):
+                    subdir=dir+"/"+subdir
+                results[subdir]={ 'name': subdir, 'type': 'directory' }
+                if recurse>0:
+                    results.update(self.getfilesystem(subdir, recurse-1))
+            except BrewNoMoreEntriesException:
+                break
+
+        # self.log("file listing 0x0b command")
+        for i in xrange(10000):
+            try:
+                req=p_brew.listfilerequest()
+                req.entrynumber=i
+                req.dirname=dir
+                res=self.sendbrewcommand(req,p_brew.listfileresponse)
+                results[res.filename]={ 'name': res.filename, 'type': 'file',
+                                'size': res.size }
+                if res.date==0:
+                    results[res.filename]['date']=(0, "")
+                else:
+                    try:
+                        date=res.date+self._brewepochtounix
+                        results[res.filename]['date']=(date, time.strftime("%x %X", time.gmtime(date)))
+                    except:
+                        # invalid date - see SF bug #833517
+                        results[res.filename]['date']=(0, "")
+                    
+            except BrewNoMoreEntriesException:
+                break
+
+        return results
+
     def statfile(self, name):
         # return the status of the file
         try:
p_lgvx8100.p (application/octet-stream, 9.7 KB) - not displayed
p_lgvx8100.p.diff (application/octet-stream, 425 B)
Index: p_lgvx8100.p
===================================================================
RCS file: /cvsroot/bitpim/bitpim/p_lgvx8100.p,v
retrieving revision 1.7
diff -u -r1.7 p_lgvx8100.p
--- p_lgvx8100.p	12 Sep 2005 05:23:42 -0000	1.7
+++ p_lgvx8100.p	25 Sep 2005 03:07:16 -0000
@@ -30,6 +30,8 @@
 SPEEDDIALINDEX=1 
 MAXCALENDARDESCRIPTION=32
 
+BREW_FILESYSTEM_TYPE=1
+
 SMS_CANNED_MAX_ITEMS=18
 SMS_CANNED_MAX_LENGTH=101
p_brew.p (application/octet-stream, 6.1 KB) - not displayed
p_brew.p.diff (application/octet-stream, 976 B)
Index: p_brew.p
===================================================================
RCS file: /cvsroot/bitpim/bitpim/p_brew.p,v
retrieving revision 1.11
diff -u -r1.11 p_brew.p
--- p_brew.p	8 Aug 2005 23:54:30 -0000	1.11
+++ p_brew.p	25 Sep 2005 03:26:37 -0000
@@ -107,6 +107,18 @@
     1 UINT dirnamelen "which portion of the filename is the directory, including the last /"
     * STRING {'terminator': None, 'pascal': True} filename 
 
+PACKET listdirectoryrequest:
+    "This gets one directory entry (directory only) at a time"
+    * requestheader {'command': 0x0a} +header
+    4 UINT entrynumber
+    * STRING {'terminator': 0, 'pascal': True} dirname
+
+PACKET listdirectoryresponse:
+    * responseheader header
+    4 UINT entrynumber  
+    17 UNKNOWN unknown1  "probably the directory attributes"
+    * STRING {'terminator': None, 'pascal': True} subdir 
+
 PACKET statfilerequest:
     "Get the status of the file"
     * requestheader { 'command': 7 } +header
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.