yet more vx8100 updates

Simon C <[email protected]>
Newsgroups gmane.comp.mobile.bitpim.devel
Message-ID <[email protected]>
1) Added call history for the vx8100.
2) Updated help.

These files replace all the files I sent out yesterday and before

Apologies for all these versions, but I didn't think I would get call history working this week, the call history class was well written and easy to understand so it did not take too long to implement.
I had to make one small fix to allow the phone class to set the 'id' of the CallHistoryEntry, the 8100 reads so fast that using the time() func does not produce unique IDs. This should not effect other users of the CallHistoryEntry class, as I left the default setting in place.

If someone could check in in please so it makes the build.

thanks,

Simon
call_history.py (text/plain, 14.8 KB)
### BITPIM
###
### Copyright (C) 2005 Joe Pham <[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: call_history.py,v 1.4 2005/07/29 03:25:30 djpham Exp $

"""
Code to handle Call History data storage and display.

The format of the Call History is standardized.  It is an object with the
following attributes:

folder: string (where this item belongs)
datetime: string 'YYYYMMDDThhmmss' or (y,m,d,h,m,s)
number: string (the phone number of this call)

To implement Call History feature for a phone module:

  Add an entry into Profile._supportedsyncs:
  ('call_history', 'read', None),

  Implement the following method in your Phone class:
  def getcallhistory(self, result, merge):
     ...
     return result

The result dict key is 'call_history'.

"""

# standard modules
import copy
import sha
import time

# wx modules
import wx
import wx.lib.scrolledpanel as scrolled

# BitPim modules
import database
import phonenumber
import pubsub
import today

#-------------------------------------------------------------------------------
class CallHistoryDataobject(database.basedataobject):
    _knownproperties=['folder', 'datetime', 'number' ]
    _knownlistproperties=database.basedataobject._knownlistproperties.copy()
    def __init__(self, data=None):
        if data is None or not isinstance(data, CallHistoryEntry):
            return;
        self.update(data.get_db_dict())
callhistoryobjectfactory=database.dataobjectfactory(CallHistoryDataobject)

#-------------------------------------------------------------------------------
class CallHistoryEntry(object):
    Folder_Incoming='Incoming'
    Folder_Outgoing='Outgoing'
    Folder_Missed='Missed'
    Folder_Data='Data'
    Valid_Folders=(Folder_Incoming, Folder_Outgoing, Folder_Missed, Folder_Data)
    _folder_key='folder'
    _datetime_key='datetime'
    _number_key='number'
    _unknown_datetime='YYYY-MM-DD hh:mm:ss'
    def __init__(self):
        self._data={ 'serials': [] }
        self._create_id()

    def __eq__(self, rhs):
        return self.folder==rhs.folder and self.datetime==rhs.datetime and\
               self.number==rhs.number
    def __ne__(self, rhs):
        return self.folder!=rhs.folder or self.datetime!=rhs.datetime or\
               self.number!=rhs.number
    def get(self):
        return copy.deepcopy(self._data, {})
    def set(self, d):
        self._data={}
        self._data.update(d)

    def get_db_dict(self):
        return self.get()
    def set_db_dict(self, d):
        self.set(d)

    def _create_id(self):
        "Create a BitPim serial for this entry"
        self._data.setdefault("serials", []).append(\
            {"sourcetype": "bitpim", "id": str(time.time())})
    def _get_id(self):
        s=self._data.get('serials', [])
        for n in s:
            if n.get('sourcetype', None)=='bitpim':
                return n.get('id', None)
        return None
    def _set_id(self, id):
        s=self._data.get('serials', [])
        for n in s:
            if n.get('sourcetype', None)=='bitpim':
                n['id']=id
                return
        self._data['serials'].append({'sourcetype': 'bitpim', 'id': id } )
    id=property(fget=_get_id, fset=_set_id)

    def _set_or_del(self, key, v, v_list=[]):
        if v is None or v in v_list:
            if self._data.has_key(key):
                del self._data[key]
        else:
            self._data[key]=v

    def _get_folder(self):
        return self._data.get(self._folder_key, '')
    def _set_folder(self, v):
        if v is None:
            if self._data.has_key(self._folder_key):
                del self._data[self._folder_key]
                return
        if not isinstance(v, (str, unicode)):
            raise TypeError,'not a string or unicode type'
        if v not in self.Valid_Folders:
            raise ValueError,'not a valid folder'
        self._data[self._folder_key]=v
    folder=property(fget=_get_folder, fset=_set_folder)
    def _get_number(self):
        return self._data.get(self._number_key, '')
    def _set_number(self, v):
        self._set_or_del(self._number_key, v, [''])
    number=property(fget=_get_number, fset=_set_number)
    def _get_datetime(self):
        return self._data.get(self._datetime_key, '')
    def _set_datetime(self, v):
        # this routine supports 2 formats:
        # (y,m,d,h,m,s) and 'YYYYMMDDThhmmss'
        # check for None and delete manually
        if v is None:
            if self._data.has_key(self._datetime_key):
                del self._data[self._datetime_key]
            return
        if isinstance(v, (tuple, list)):
            if len(v)!=6:
                raise ValueError,'(y, m, d, h, m, s)'
            s='%04d%02d%02dT%02d%02d%02d'%tuple(v)
        elif isinstance(v, (str, unicode)):
            # some primitive validation
            if len(v)!=15 or v[8]!='T':
                raise ValueError,'value must be in format YYYYMMDDThhmmss'
            s=v
        else:
            raise TypeError
        self._data[self._datetime_key]=s
    datetime=property(fget=_get_datetime, fset=_set_datetime)
    def get_repr(self, name=None):
        # return a string representing this item in the format of
        # YYYY-MM-DD hh:mm:ss <Number/Name>
        f=self.folder[0].upper()
        s=self.datetime
        if not len(s):
            s=f+'['+self._unknown_datetime+']'
        else:
            s=f+'['+s[:4]+'-'+s[4:6]+'-'+s[6:8]+' '+s[9:11]+':'+s[11:13]+':'+s[13:]+']  -  '
        if name is not None:
            s+=name
        else:
            s+=phonenumber.format(self.number)
        return s
    def summary(self, name=None):
        # return a short summary for this entry in the format of
        # MM/DD hh:mm <Number/Name>
        s=self.datetime
        if s:
            s=s[4:6]+'/'+s[6:8]+' '+s[9:11]+':'+s[11:13]+' '
        else:
            s='**/** **:** '
        if name:
            s+=name
        else:
            s+=phonenumber.format(self.number)
        return s

    def _get_date_str(self):
        s=self.datetime
        if not len(s):
            return '****-**-**'
        else:
            return s[:4]+'-'+s[4:6]+'-'+s[6:8]
    date_str=property(fget=_get_date_str)

#-------------------------------------------------------------------------------
class CallHistoryWidget(scrolled.ScrolledPanel):
    _data_key='call_history'
    _by_type=0
    _by_date=1
    _by_number=2
    def __init__(self, mainwindow, parent):
        super(CallHistoryWidget, self).__init__(parent, -1)
        self._main_window=mainwindow
        self._data={}
        self._node_dict={}
        self._name_map={}
        self._by_mode=self._by_type
        self._display_func=(self._display_by_type, self._display_by_date,
                             self._display_by_number)
        # main box sizer
        vbs=wx.BoxSizer(wx.VERTICAL)
        self._item_list=wx.TreeCtrl(self, wx.NewId())
        vbs.Add(self._item_list, 1, wx.EXPAND|wx.ALL, 5)
        self._root=self._item_list.AddRoot('Call History')
        self._nodes={}
        # context menu
        organize_menu=wx.Menu()
        organize_menu_data=(
            ('Type', self._OnOrganizedByType),
            ('Date', self._OnOrganizedByDate),
            ('Number', self._OnOrganizedByNumber))
        for e in organize_menu_data:
            id=wx.NewId()
            organize_menu.AppendRadioItem(id, e[0])
            wx.EVT_MENU(self, id, e[1])
        context_menu_data=(
            ('Expand All', self._OnExpandAll),
            ('Collapse All', self._OnCollapseAll))
        self._bgmenu=wx.Menu()
        self._bgmenu.AppendMenu(wx.NewId(), 'Organize Items by', organize_menu)
        for e in context_menu_data:
            id=wx.NewId()
            self._bgmenu.Append(id, e[0])
            wx.EVT_MENU(self, id, e[1])
        # event handlers
        pubsub.subscribe(self._OnPBLookup, pubsub.RESPONSE_PB_LOOKUP)
        wx.EVT_RIGHT_UP(self._item_list, self._OnRightClick)
        # all done
        self.SetSizer(vbs)
        self.SetAutoLayout(True)
        vbs.Fit(self)
        self.SetupScrolling()
        # populate data
        self._populate()

    def _OnPBLookup(self, msg):
        d=msg.data
        k=d.get('item', None)
        name=d.get('name', None)
        if k is None:
            return
        self._name_map[k]=name

    def _OnRightClick(self, evt):
        self._item_list.PopupMenu(self._bgmenu, evt.GetPosition())
    def _OnOrganizedByType(self, evt):
        evt.GetEventObject().Check(evt.GetId(), True)
        if self._by_mode!=self._by_type:
            self._by_mode=self._by_type
            self._display_func[self._by_type]()
            self._expand_all()
    def _OnOrganizedByDate(self, evt):
        evt.GetEventObject().Check(evt.GetId(), True)
        if self._by_mode!=self._by_date:
            self._by_mode=self._by_date
            self._display_func[self._by_date]()
            self._expand_all()
    def _OnOrganizedByNumber(self, evt):
        evt.GetEventObject().Check(evt.GetId(), True)
        if self._by_mode!=self._by_number:
            self._by_mode=self._by_number
            self._display_func[self._by_number]()
            self._expand_all()

    def _expand_all(self, sel_id=None):
        if sel_id is None:
            sel_id=self._root
        self._item_list.Expand(sel_id)
        id, cookie=self._item_list.GetFirstChild(sel_id)
        while id.IsOk():
            self._item_list.Expand(id)
            id, cookie=self._item_list.GetNextChild(sel_id, cookie)
    def _OnExpandAll(self, _):
        sel_id=self._item_list.GetSelection()
        if not sel_id.IsOk():
            sel_id=self._root
        self._expand_all(sel_id)
    def _OnCollapseAll(self, _):
        sel_id=self._item_list.GetSelection()
        if not sel_id.IsOk():
            sel_id=self._root
        self._item_list.Collapse(sel_id)
        id, cookie=self._item_list.GetFirstChild(sel_id)
        while id.IsOk():
            self._item_list.Collapse(id)
            id, cookie=self._item_list.GetNextChild(sel_id, cookie)

    def _clear(self):
        self._item_list.Collapse(self._root)
        for k,e in self._nodes.items():
            self._item_list.DeleteChildren(e)
    def _display_by_date(self):
        self._item_list.CollapseAndReset(self._root)
        self._nodes={}
        # go through our data to collect the dates
        date_list=[]
        for k,e in self._data.items():
            if e.date_str not in date_list:
                date_list.append(e.date_str)
        date_list.sort()
        for s in date_list:
            self._nodes[s]=self._item_list.AppendItem(self._root, s)
        # build the tree
        for k,e in self._data.items():
            i=self._item_list.AppendItem(self._nodes[e.date_str],
                                          e.get_repr(self._name_map.get(e.number, None)))
            self._item_list.SetItemPyData(i, k)

    def _display_by_number(self):
        self._item_list.CollapseAndReset(self._root)
        self._nodes={}
        # go through our data to collect the numbers
        number_list=[]
        for k,e in self._data.items():
            s=phonenumber.format(e.number)
            if s not in number_list:
                number_list.append(s)
        number_list.sort()
        for s in number_list:
            self._nodes[s]=self._item_list.AppendItem(self._root, s)
        # build the tree
        for k,e in self._data.items():
            i=self._item_list.AppendItem(self._nodes[phonenumber.format(e.number)],
                                          e.get_repr(self._name_map.get(e.number, None)))
            self._item_list.SetItemPyData(i, k)
        
    def _display_by_type(self):
        self._item_list.CollapseAndReset(self._root)
        self._nodes={}
        for s in CallHistoryEntry.Valid_Folders:
            self._nodes[s]=self._item_list.AppendItem(self._root, s)
        node_dict={}
        for k,e in self._data.items():
            node_dict[e.get_repr(self._name_map.get(e.number, None))]=k
        keys=node_dict.keys()
        keys.sort()
        for k in keys:
            data_key=node_dict[k]
            n=self._data[data_key]
            i=self._item_list.AppendItem(self._nodes[n.folder], k)
            self._item_list.SetItemPyData(i, data_key)

    def _publish_today_data(self):
        keys=self._data.keys()
        keys.sort()
        keys.reverse()
        today_event=today.TodayIncomingCallsEvent()
        today_event.names=[self._data[k].summary(self._name_map.get(self._data[k].number, None))\
                                    for k in keys \
                                    if self._data[k].folder==CallHistoryEntry.Folder_Incoming]
        today_event.broadcast()
        today_event=today.TodayMissedCallsEvent()
        today_event.names=[self._data[k].summary(self._name_map.get(self._data[k].number, None))\
                                    for k in keys \
                                    if self._data[k].folder==CallHistoryEntry.Folder_Missed]
        today_event.broadcast()

    def _populate(self):
        self._clear()
        self._node_dict={}
        # lookup phone book for names
        for k,e in self._data.items():
            if not self._name_map.has_key(e.number):
                pubsub.publish(pubsub.REQUEST_PB_LOOKUP,
                               { 'item': e.number } )
        self._display_func[self._by_mode]()
        self._publish_today_data()
            
    def OnDelete(self, _):
        sel_idx=self._item_list.GetSelection()
        if not sel_idx.Ok():
            return
        k=self._item_list.GetPyData(sel_idx)
        if k is None:
            # this is not a leaf node
            return
        self._item_list.Delete(sel_idx)
        del self._data[k]
        self._save_to_db(self._data)

    def getdata(self, dict, want=None):
        dict[self._data_key]=copy.deepcopy(self._data)
    def populate(self, dict):
        self._data=dict.get(self._data_key, {})
        self._populate()
    def _save_to_db(self, dict):
        db_rr={}
        for k,e in dict.items():
            db_rr[k]=CallHistoryDataobject(e)
        database.ensurerecordtype(db_rr, callhistoryobjectfactory)
        self._main_window.database.savemajordict(self._data_key, db_rr)

    def populatefs(self, dict):
        self._save_to_db(dict.get(self._data_key, {}))
        return dict
    def getfromfs(self, result):
        dict=self._main_window.database.\
                   getmajordictvalues(self._data_key,
                                      callhistoryobjectfactory)
        r={}
        for k,e in dict.items():
            ce=CallHistoryEntry()
            ce.set_db_dict(e)
            r[ce.id]=ce
        result.update({ self._data_key: r})
        return result
    def merge(self, dict):
        d=dict.get(self._data_key, {})
        l=[e for k,e in self._data.items()]
        for k,e in d.items():
            if e not in l:
                self._data[e.id]=e
        self._save_to_db(self._data)
        self._populate()
com_lgvx8100.py (text/plain, 15.9 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 time
import cStringIO
import sha

# my modules
import common
import copy
import p_lgvx8100
import com_lgvx7000
import com_brew
import com_phone
import com_lg
import prototypes
import bpcalendar
import call_history


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/ms', 100, 50, 1),
        )

    builtinwallpapers = () # none

    wallpaperlocations= (
        ( 'images', 'dload/image.dat', 'dload/imagesize.dat', 'brew/16452/mp', 100, 50, 0),
        )
        
    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 getcallhistory(self, result):
        res={}
        # read the incoming call history file
        self._readhistoryfile("pim/missed_log.dat", 'Missed', res)
        self._readhistoryfile("pim/outgoing_log.dat", 'Outgoing', res)
        self._readhistoryfile("pim/incoming_log.dat", 'Incoming', res)
        result['call_history']=res
        return result

    def _readhistoryfile(self, fname, folder, res):
        try:
            buf=prototypes.buffer(self.getfilecontents(fname))
            ch=self.protocolclass.callhistory()
            ch.readfrombuffer(buf)
            self.logdata("Call History", buf.getdata(), ch)
            for call in ch.calls:
                if call.number=='': #empty record
                    break
                entry=call_history.CallHistoryEntry()
                entry.folder=folder
                # convert from GPS to unix time
                unixtime=315964800+call.GPStime
                t=time.gmtime(315964800+call.GPStime)
                entry.datetime=((t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec))
                entry.number=call.number
                #use the timestamp as the ID, it is unique
                entry.id=call.GPStime
                res[entry.id]=entry
        except com_brew.BrewNoSuchFileException:
            pass # do nothing if file doesn't exist
        return


    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.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={}
        keys=cal.keys()
        keys.sort()
        pos=1

        # number of entries
        eventsf.numactiveitems=len(keys)
        
        # 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:
                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

            # put entry in nice shiny new dict we are building
            entry=copy.copy(entry)
            newcal[data.pos]=entry
            eventsf.events.append(data)

        # scribble everything out
        buf=prototypes.buffer()
        eventsf.writetobuffer(buf)
        self.logdata("Writing calendar", buf.getvalue(), eventsf)
        self.writefile("sch/newschedule.dat", buf.getvalue())
        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
        # bitpim does not support vibrate so turn it off for all alarms
        vibrate=1
        if entry.alarm>=2880:
            entry.alarm=2880
            data.alarmminutes=0
            data.alarmhours=48
            data.alarmindex_vibrate=0x10+vibrate
        if entry.alarm>=1440:
            entry.alarm=1440
            data.alarmminutes=0
            data.alarmhours=24
            data.alarmindex_vibrate=0xe+vibrate
        if entry.alarm>=120:
            entry.alarm=120
            data.alarmminutes=0
            data.alarmhours=2
            data.alarmindex_vibrate=0xc+vibrate
        elif entry.alarm>=60:
            entry.alarm=60
            data.alarmminutes=0
            data.alarmhours=1
            data.alarmindex_vibrate=0xa+vibrate
        elif entry.alarm>=15:
            entry.alarm=15
            data.alarmminutes=15
            data.alarmhours=0
            data.alarmindex_vibrate=0x8+vibrate
        elif entry.alarm>=10:
            entry.alarm=10
            data.alarmminutes=10
            data.alarmhours=0
            data.alarmindex_vibrate=0x6+vibrate
        elif entry.alarm>=5:
            entry.alarm=5
            data.alarmminutes=10
            data.alarmhours=0
            data.alarmindex_vibrate=0x4+vibrate
        elif entry.alarm>=0:
            entry.alarm=0
            data.alarmminutes=0
            data.alarmhours=0
            data.alarmindex_vibrate=0x2+vibrate
        else: # no alarm
            data.alarmminutes=0x64
            data.alarmhours=0x64
            data.alarmindex_vibrate=1
        return

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

    BP_Calendar_Version=3

    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 8010 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
        ('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'),
        ('call_history', 'read', None),
        )
com_lgvx4400.py (text/plain, 39.5 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_lgvx4400.py,v 1.128 2005/05/23 05:58:29 rogerb Exp $

"""Communicate with the LG VX4400 cell phone"""

# standard modules
import re
import time
import cStringIO
import sha

# my modules
import common
import copy
import p_lgvx4400
import com_brew
import com_phone
import com_lg
import prototypes
import fileinfo

class Phone(com_phone.Phone,com_brew.BrewProtocol,com_lg.LGPhonebook,com_lg.LGIndexedMedia):
    "Talk to the LG VX4400 cell phone"

    desc="LG-VX4400"
    wallpaperindexfilename="dloadindex/brewImageIndex.map"
    ringerindexfilename="dloadindex/brewRingerIndex.map"
    protocolclass=p_lgvx4400
    serialsname='lgvx4400'

    imagelocations=(
        # offset, index file, files location, type, maximumentries
        ( 10, "dloadindex/brewImageIndex.map", "brew/shared", "images", 30),
        )

    ringtonelocations=(
        # offset, index file, files location, type, maximumentries
        ( 50, "dloadindex/brewRingerIndex.map", "user/sound/ringer", "ringers", 30),
        )

    builtinimages=('Balloons', 'Soccer', 'Basketball', 'Bird',
                   'Sunflower', 'Puppy', 'Mountain House', 'Beach')

    builtinringtones=( 'Ring 1', 'Ring 2', 'Ring 3', 'Ring 4', 'Ring 5',
                       'Ring 6', 'Voices of Spring', 'Twinkle Twinkle',
                       'The Toreadors', 'Badinerie', 'The Spring', 'Liberty Bell',
                       'Trumpet Concerto', 'Eine Kleine', 'Silken Ladder', 'Nocturne',
                       'Csikos Post', 'Turkish March', 'Mozart Aria', 'La Traviata',
                       'Rag Time', 'Radetzky March', 'Can-Can', 'Sabre Dance', 'Magic Flute',
                       'Carmen' )

    
    def __init__(self, logtarget, commport):
        "Calls all the constructors and sets initial modes"
        com_phone.Phone.__init__(self, logtarget, commport)
        com_brew.BrewProtocol.__init__(self)
        com_lg.LGPhonebook.__init__(self)
        com_lg.LGIndexedMedia.__init__(self)
        self.log("Attempting to contact phone")
        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]={ 'icon': g.groups[i].icon, 'name': g.groups[i].name }
        results['groups']=groups
        self.getwallpaperindices(results)
        self.getringtoneindices(results)
        self.log("Fundamentals retrieved")
        return results


    def getwallpaperindices(self, results):
        return self.getmediaindex(self.builtinimages, self.imagelocations, results, 'wallpaper-index')

    def getringtoneindices(self, results):
        return self.getmediaindex(self.builtinringtones, self.ringtonelocations, results, 'ringtone-index')

    def getphonebook(self,result):
        """Reads the phonebook data.  The L{getfundamentals} information will
        already be in result."""
        # Read speed dials first
        speeds={}
        try:
            if self.protocolclass.NUMSPEEDDIALS:
                self.log("Reading speed dials")
                buf=prototypes.buffer(self.getfilecontents("pim/pbspeed.dat"))
                sd=self.protocolclass.speeddials()
                sd.readfrombuffer(buf)
                for i in range(self.protocolclass.FIRSTSPEEDDIAL, self.protocolclass.LASTSPEEDDIAL+1):
                    if sd.speeddials[i].entry<0 or sd.speeddials[i].entry>self.protocolclass.NUMPHONEBOOKENTRIES:
                        continue
                    l=speeds.get(sd.speeddials[i].entry, [])
                    l.append((i, sd.speeddials[i].number - self.protocolclass.SPEEDDIALOFFSET))
                    speeds[sd.speeddials[i].entry]=l
        except com_brew.BrewNoSuchFileException:
            pass

        pbook={}
        # Bug in the phone.  if you repeatedly read the phone book it starts
        # returning a random number as the number of entries.  We get around
        # this by switching into brew mode which clears that.
        self.mode=self.MODENONE
        self.setmode(self.MODEBREW)
        self.log("Reading number of phonebook entries")
        req=self.protocolclass.pbinforequest()
        res=self.sendpbcommand(req, self.protocolclass.pbinforesponse)
        numentries=res.numentries
        if numentries<0 or numentries>1000:
            self.log("The phone is lying about how many entries are in the phonebook so we are doing it the hard way")
            numentries=0
            firstserial=None
            loop=xrange(0,1000)
            hardway=True
        else:
            self.log("There are %d entries" % (numentries,))
            loop=xrange(0, numentries)
            hardway=False
        # reset cursor
        self.sendpbcommand(self.protocolclass.pbinitrequest(), self.protocolclass.pbinitresponse)
        problemsdetected=False
        dupecheck={}
        for i in loop:
            if hardway:
                numentries+=1
            req=self.protocolclass.pbreadentryrequest()
            res=self.sendpbcommand(req, self.protocolclass.pbreadentryresponse)
            self.log("Read entry "+`i`+" - "+res.entry.name)
            entry=self.extractphonebookentry(res.entry, speeds, result)
            if hardway and firstserial is None:
                firstserial=res.entry.serial1
            pbook[i]=entry
            if res.entry.serial1 in dupecheck:
                self.log("Entry %s has same serial as entry %s.  This will cause problems." % (`entry`, dupecheck[res.entry.serial1]))
                problemsdetected=True
            else:
                dupecheck[res.entry.serial1]=entry
            self.progress(i, numentries, res.entry.name)
            #### Advance to next entry
            req=self.protocolclass.pbnextentryrequest()
            res=self.sendpbcommand(req, self.protocolclass.pbnextentryresponse)
            if hardway:
                # look to see if we have looped
                if res.serial==firstserial or res.serial==0:
                    break

        self.progress(numentries, numentries, "Phone book read completed")

        if problemsdetected:
            self.log("There are duplicate serial numbers.  See above for details.")
            raise common.IntegrityCheckFailed(self.desc, "Data in phonebook is inconsistent.  There are multiple entries with the same serial number.  See the log.")

        result['phonebook']=pbook
        cats=[]
        for i in result['groups']:
            if result['groups'][i]['name']!='No Group':
                cats.append(result['groups'][i]['name'])
        result['categories']=cats
        print "returning keys",result.keys()
        return pbook

    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.icon=groups[k]['icon']
            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 savephonebook(self, data):
        "Saves out the phonebook"
        self.savegroups(data)
 
        progressmax=len(data['phonebook'].keys())
        # if we are going to write out speeddials, we have to re-read the entire
        # phonebook again
        if data.get('speeddials',None) is not None:
            progressmax+=len(data['phonebook'].keys())

        # To write the phone book, we scan through all existing entries
        # and record their record number and serials.
        # We then delete any entries that aren't in data
        # We then write out our records, using overwrite or append
        # commands as necessary
        serialupdates=[]
        existingpbook={} # keep track of the phonebook that is on the phone
        self.mode=self.MODENONE
        self.setmode(self.MODEBREW) # see note in getphonebook() for why this is necessary
        self.setmode(self.MODEPHONEBOOK)
        # similar loop to reading
        req=self.protocolclass.pbinforequest()
        res=self.sendpbcommand(req, self.protocolclass.pbinforesponse)
        numexistingentries=res.numentries
        if numexistingentries<0 or numexistingentries>1000:
            self.log("The phone is lying about how many entries are in the phonebook so we are doing it the hard way")
            numexistingentries=0
            firstserial=None
            loop=xrange(0,1000)
            hardway=True
        else:
            self.log("There are %d existing entries" % (numexistingentries,))
            progressmax+=numexistingentries
            loop=xrange(0, numexistingentries)
            hardway=False
        progresscur=0
        # reset cursor
        self.sendpbcommand(self.protocolclass.pbinitrequest(), self.protocolclass.pbinitresponse)
        for i in loop:
            ### Read current entry
            if hardway:
                numexistingentries+=1
                progressmax+=1
            req=self.protocolclass.pbreadentryrequest()
            res=self.sendpbcommand(req, self.protocolclass.pbreadentryresponse)
            
            entry={ 'number':  res.entry.entrynumber, 'serial1':  res.entry.serial1,
                    'serial2': res.entry.serial2, 'name': res.entry.name}
            assert entry['serial1']==entry['serial2'] # always the same
            self.log("Reading entry "+`i`+" - "+entry['name'])
            if hardway and firstserial is None:
                firstserial=res.entry.serial1
            existingpbook[i]=entry 
            self.progress(progresscur, progressmax, "existing "+entry['name'])
            #### Advance to next entry
            req=self.protocolclass.pbnextentryrequest()
            res=self.sendpbcommand(req, self.protocolclass.pbnextentryresponse)
            progresscur+=1
            if hardway:
                # look to see if we have looped
                if res.serial==firstserial or res.serial==0:
                    break
        # we have now looped around back to begining

        # Find entries that have been deleted
        pbook=data['phonebook']
        dellist=[]
        for i in range(0, numexistingentries):
            ii=existingpbook[i]
            serial=ii['serial1']
            item=self._findserial(serial, pbook)
            if item is None:
                dellist.append(i)

        progressmax+=len(dellist) # more work to do

        # Delete those entries
        for i in dellist:
            progresscur+=1
            numexistingentries-=1  # keep count right
            ii=existingpbook[i]
            self.log("Deleting entry "+`i`+" - "+ii['name'])
            req=self.protocolclass.pbdeleteentryrequest()
            req.serial1=ii['serial1']
            req.serial2=ii['serial2']
            req.entrynumber=ii['number']
            self.sendpbcommand(req, self.protocolclass.pbdeleteentryresponse)
            self.progress(progresscur, progressmax, "Deleting "+ii['name'])
            # also remove them from existingpbook
            del existingpbook[i]

        # counter to keep track of record number (otherwise appends don't work)
        counter=0
        # Now rewrite out existing entries
        keys=existingpbook.keys()
        existingserials=[]
        keys.sort()  # do in same order as existingpbook
        for i in keys:
            progresscur+=1
            ii=pbook[self._findserial(existingpbook[i]['serial1'], pbook)]
            self.log("Rewriting entry "+`i`+" - "+ii['name'])
            self.progress(progresscur, progressmax, "Rewriting "+ii['name'])
            entry=self.makeentry(counter, ii, data)
            counter+=1
            existingserials.append(existingpbook[i]['serial1'])
            req=self.protocolclass.pbupdateentryrequest()
            req.entry=entry
            res=self.sendpbcommand(req, self.protocolclass.pbupdateentryresponse)
            serialupdates.append( ( ii["bitpimserial"],
                                    {'sourcetype': self.serialsname, 'serial1': res.serial1, 'serial2': res.serial1,
                                     'sourceuniqueid': data['uniqueserial']})
                                  )
            assert ii['serial1']==res.serial1 # serial should stay the same

        # Finally write out new entries
        keys=pbook.keys()
        keys.sort()
        for i in keys:
            ii=pbook[i]
            if ii['serial1'] in existingserials:
                continue # already wrote this one out
            progresscur+=1
            entry=self.makeentry(counter, ii, data)
            counter+=1
            self.log("Appending entry "+ii['name'])
            self.progress(progresscur, progressmax, "Writing "+ii['name'])
            req=self.protocolclass.pbappendentryrequest()
            req.entry=entry
            res=self.sendpbcommand(req, self.protocolclass.pbappendentryresponse)
            serialupdates.append( ( ii["bitpimserial"],
                                     {'sourcetype': self.serialsname, 'serial1': res.newserial, 'serial2': res.newserial,
                                     'sourceuniqueid': data['uniqueserial']})
                                  )
        data["serialupdates"]=serialupdates
        # deal with the speeddials
        if data.get("speeddials",None) is not None:
            # Yes, we have to read the ENTIRE phonebook again.  This
            # is because we don't know which entry numbers actually
            # got assigned to the various entries, and we need the
            # actual numbers to assign to the speed dials
            newspeeds={}
            if len(data['speeddials']):
                # Move cursor to begining of phonebook
                self.mode=self.MODENONE
                self.setmode(self.MODEBREW) # see note in getphonebook() for why this is necessary
                self.setmode(self.MODEPHONEBOOK)
                self.log("Searching for speed dials")
                self.sendpbcommand(self.protocolclass.pbinitrequest(), self.protocolclass.pbinitresponse)
                for i in range(len(pbook)):
                    ### Read current entry
                    req=self.protocolclass.pbreadentryrequest()
                    res=self.sendpbcommand(req, self.protocolclass.pbreadentryresponse)
                    self.log("Scanning "+res.entry.name)
                    progresscur+=1
                    # we have to turn the entry serial number into a bitpim serial
                    serial=res.entry.serial1
                    found=False
                    for bps, serials in serialupdates:
                        if serials['serial1']==serial:
                            # found the entry
                            for sd in data['speeddials']:
                                xx=data['speeddials'][sd]
                                if xx[0]==bps:
                                    found=True
                                    newspeeds[sd]=(res.entry.entrynumber, xx[1] + self.protocolclass.SPEEDDIALOFFSET)
                                    nt=self.protocolclass.numbertypetab[res.entry.numbertypes[xx[1]].numbertype]
                                    self.log("Speed dial #%d = %s (%s/%d)" % (sd, res.entry.name, nt, xx[1]))
                                    self.progress(progresscur, progressmax, "Speed dial #%d = %s (%s/%d)" % (sd, res.entry.name, nt, xx[1]))
                    if not found:
                        self.progress(progresscur, progressmax, "Scanning "+res.entry.name)
                    # move to next entry
                    self.sendpbcommand(self.protocolclass.pbnextentryrequest(), self.protocolclass.pbnextentryresponse)

            self.progress(progressmax, progressmax, "Finished scanning")
            print "new speed dials is",newspeeds
            req=self.protocolclass.speeddials()
            for i in range(self.protocolclass.NUMSPEEDDIALS):
                sd=self.protocolclass.speeddial()
                if i in newspeeds:
                    sd.entry=newspeeds[i][0]
                    sd.number=newspeeds[i][1]
                req.speeddials.append(sd)
            buffer=prototypes.buffer()
            req.writetobuffer(buffer)

            # We check the existing speed dial file as changes require a reboot
            self.log("Checking existing speed dials")
            if buffer.getvalue()!=self.getfilecontents("pim/pbspeed.dat"):
                self.logdata("New speed dial file", buffer.getvalue(), req)
                self.writefile("pim/pbspeed.dat", buffer.getvalue())
                self.log("Your phone has to be rebooted due to the speed dials changing")
                self.progress(progressmax, progressmax, "Rebooting phone")
                data["rebootphone"]=True
            else:
                self.log("No changes to speed dials")

        return data
        

    def _findserial(self, serial, dict):
        """Searches dict to find entry with matching serial.  If not found,
        returns None"""
        for i in dict:
            if dict[i]['serial1']==serial:
                return i
        return None
            
    def getcalendar(self,result):
        res={}
        # Read exceptions file first
        try:
            buf=prototypes.buffer(self.getfilecontents("sch/schexception.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/schedule.dat"))
            if len(buf.getdata())<2:
                # 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:
                entry={}
                entry['pos']=event.pos
                if entry['pos']==-1: continue # blanked entry
                # normal fields
                for field in 'start','end','daybitmap','changeserial','snoozedelay','ringtone','description':
                    entry[field]=getattr(event,field)
                # calculated ones
                try:
                    entry['repeat']=self._calrepeatvalues[event.repeat]
                except KeyError:
                    entry['repeat']=None
                min=event.alarmminutes
                hour=event.alarmhours
                if min==100 or hour==100:
                    entry['alarm']=None # no alarm set
                else:
                    entry['alarm']=hour*60+min
                # Exceptions
                if exceptions.has_key(event.pos):
                    entry['exceptions']=exceptions[event.pos]
                res[event.pos]=entry

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

    def savecalendar(self, dict, merge):
        # ::TODO:: obey merge param
        # what will be written to the files
        eventsf=self.protocolclass.schedulefile()
        exceptionsf=self.protocolclass.scheduleexceptionfile()

        # what are we working with
        cal=dict['calendar']
        newcal={}
        keys=cal.keys()
        keys.sort()

        # number of entries
        eventsf.numactiveitems=len(keys)
        
        # 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()
            entry['pos']=data.pos
            # simple copy of these fields
            for field in 'start', 'end', 'daybitmap', 'changeserial', 'snoozedelay','ringtone','description':
                v=entry[field]
                if field == "description":
                    v=v[:self.protocolclass.MAXCALENDARDESCRIPTION]
                setattr(data,field,v)
            # And now the special ones
            repeat=None
            for k,v in self._calrepeatvalues.items():
                if entry['repeat']==v:
                    repeat=k
                    break
            assert repeat is not None
            data.repeat=repeat
            # alarm 100 indicates not set
            if entry['alarm'] is None or entry['alarm']<0:
                hour=0xFF
                min=0xFF
            else:
                assert entry['alarm']>=0
                hour=entry['alarm']/60
                min=entry['alarm']%60
            data.alarmminutes=min
            data.alarmhours=hour

            # update exceptions if needbe
            if entry.has_key('exceptions'):
                for y,m,d in entry['exceptions']:
                    de=self.protocolclass.scheduleexception()
                    de.pos=data.pos
                    de.day=d
                    de.month=m
                    de.year=y
                    exceptionsf.items.append(de)

            # put entry in nice shiny new dict we are building
            entry=copy.copy(entry)
            newcal[data.pos]=entry
            eventsf.events.append(data)

        # scribble everything out
        buf=prototypes.buffer()
        eventsf.writetobuffer(buf)
        self.logdata("Writing calendar", buf.getvalue(), eventsf)
        self.writefile("sch/schedule.dat", buf.getvalue())
        buf=prototypes.buffer()
        exceptionsf.writetobuffer(buf)
        self.logdata("Writing calendar exceptions", buf.getvalue(), exceptionsf)
        self.writefile("sch/schexception.dat", buf.getvalue())

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

        return dict
        

    _calrepeatvalues={
        0x10: None,
        0x11: 'daily',
        0x12: 'monfri',
        0x13: 'weekly',
        0x14: 'monthly',
        0x15: 'yearly'
        }
    

    def _normaliseindices(self, d):
        "turn all negative keys into positive ones for index"
        res={}
        keys=d.keys()
        keys.sort()
        keys.reverse()
        for k in keys:
            if k<0:
                for c in range(999999):
                    if c not in keys and c not in res:
                        break
                res[c]=d[k]
            else:
                res[k]=d[k]
        return res

    def extractphonebookentry(self, entry, speeds, fundamentals):
        """Return a phonebook entry in BitPim format.  This is called from getphonebook."""
        res={}
        # serials
        res['serials']=[ {'sourcetype': self.serialsname, 'serial1': entry.serial1, 'serial2': entry.serial2,
                          'sourceuniqueid': fundamentals['uniqueserial']} ] 
        # only one name
        res['names']=[ {'full': entry.name} ]
        # only one category
        cat=fundamentals['groups'].get(entry.group, {'name': "No Group"})['name']
        if cat!="No Group":
            res['categories']=[ {'category': cat} ]
        # emails
        res['emails']=[]
        for i in entry.emails:
            if len(i.email):
                res['emails'].append( {'email': i.email} )
        if not len(res['emails']): del res['emails'] # it was empty
        # urls
        if 'url' in entry.getfields() and len(entry.url):
            res['urls']=[ {'url': entry.url} ]
        # private
        if 'secret' in entry.getfields() and entry.secret:
            # we only supply secret if it is true
            res['flags']=[ {'secret': entry.secret } ]
        # memos
        if  'memo' in entry.getfields() and len(entry.memo):
            res['memos']=[ {'memo': entry.memo } ]
        # wallpapers
        if entry.wallpaper!=self.protocolclass.NOWALLPAPER:
            try:
                paper=fundamentals['wallpaper-index'][entry.wallpaper]['name']
                res['wallpapers']=[ {'wallpaper': paper, 'use': 'call'} ]                
            except:
                print "can't find wallpaper for index",entry.wallpaper
                pass
            
        # ringtones
        res['ringtones']=[]
        if 'ringtone' in entry.getfields() and entry.ringtone!=self.protocolclass.NORINGTONE:
            try:
                tone=fundamentals['ringtone-index'][entry.ringtone]['name']
                res['ringtones'].append({'ringtone': tone, 'use': 'call'})
            except:
                print "can't find ringtone for index",entry.ringtone
        if 'msgringtone' in entry.getfields() and entry.msgringtone!=self.protocolclass.NOMSGRINGTONE:
            try:
                tone=fundamentals['ringtone-index'][entry.msgringtone]['name']
                res['ringtones'].append({'ringtone': tone, 'use': 'message'})
            except:
                print "can't find ringtone for index",entry.msgringtone
        if len(res['ringtones'])==0:
            del res['ringtones']
                    
        # numbers
        res['numbers']=[]
        for i in range(self.protocolclass.NUMPHONENUMBERS):
            num=entry.numbers[i].number
            type=entry.numbertypes[i].numbertype
            if len(num):
                t=self.protocolclass.numbertypetab[type]
                if t[-1]=='2':
                    t=t[:-1]
                res['numbers'].append({'number': num, 'type': t})
        # speed dials
        if entry.entrynumber in speeds:
            for speeddial,numberindex in speeds[entry.entrynumber]:
                try:
                    res['numbers'][numberindex]['speeddial']=speeddial
                except IndexError:
                    print "speed dial refers to non-existent number\n",res['numbers'],"\n",numberindex,speeddial
        return res

    def _findmediainindex(self, index, name, pbentryname, type):
        if type=="ringtone": default=self.protocolclass.NORINGTONE
        elif type=="message ringtone": default=self.protocolclass.NOMSGRINGTONE
        elif type=="wallpaper": default=self.protocolclass.NOWALLPAPER
        else:
            assert False, "unknown type "+type
            
        if name is None:
            return default
        for i in index:
            if index[i]['name']==name:
                return i
        self.log("%s: Unable to find %s %s in the index. Setting to default." % (pbentryname, type, name))
        return default
                    
    def makeentry(self, counter, entry, data):
        """Creates pbentry object

        @param counter: The new entry number
        @param entry:   The phonebook object (as returned from convertphonebooktophone) that we
                        are using as the source
        @param data:    The main dictionary, which we use to get access to media indices amongst
                        other things
                        """
        e=self.protocolclass.pbentry()
        e.entrynumber=counter
        
        for k in entry:
            # special treatment for lists
            if k in ('emails', 'numbers', 'numbertypes'):
                l=getattr(e,k)
                for item in entry[k]:
                    l.append(item)
            elif k=='ringtone':
                e.ringtone=self._findmediainindex(data['ringtone-index'], entry['ringtone'], entry['name'], 'ringtone')
            elif k=='msgringtone':
                e.msgringtone=self._findmediainindex(data['ringtone-index'], entry['msgringtone'], entry['name'], 'message ringtone')
            elif k=='wallpaper':
                e.wallpaper=self._findmediainindex(data['wallpaper-index'], entry['wallpaper'], entry['name'], 'wallpaper')
            elif k in e.getfields():
                # everything else we just set
                setattr(e,k,entry[k])

        return e

    smspatterns={'Inbox': re.compile(r"^.*/inbox[0-9][0-9][0-9]\.dat$"),
                 'Sent': re.compile(r"^.*/outbox[0-9][0-9][0-9]\.dat$"),
                 'Saved': re.compile(r"^.*/sf[0-9][0-9]\.dat$"),
                 }

    def getsms(self, results):
        for item in self.getfilesystem("sms").values():
            if item['type']=='file':
                folder=None
                for f,pat in self.smspatterns.items():
                    print item['name']
                    if pat.match(item['name']):
                        folder=f
                        break
                if folder is None: continue
                buf=prototypes.buffer(self.getfilecontents(item['name']))
                sf=self.protocolclass.SMSFile()
                sf.readfrombuffer(buf)
                self.logdata("SMS message in file "+item['name'], buf.getdata(), sf)

        return results
                                      
        


def phonize(str):
    """Convert the phone number into something the phone understands
    
    All digits, P, T, * and # are kept, everything else is removed"""
    return re.sub("[^0-9PT#*]", "", str)

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

    WALLPAPER_WIDTH=120
    WALLPAPER_HEIGHT=98
    MAX_WALLPAPER_BASENAME_LENGTH=19
    WALLPAPER_FILENAME_CHARS="abcdefghijklmnopqrstuvwxyz0123456789 ."
    WALLPAPER_CONVERT_FORMAT="bmp"
    
    MAX_RINGTONE_BASENAME_LENGTH=19
    RINGTONE_FILENAME_CHARS="abcdefghijklmnopqrstuvwxyz0123456789 ."
    
    # which usb ids correspond to us
    usbids_straight=( ( 0x1004, 0x6000, 2), )# VID=LG Electronics, PID=LG VX4400/VX6000 -internal USB diagnostics interface
    usbids_usbtoserial=(
        ( 0x067b, 0x2303, None), # VID=Prolific, PID=USB to serial
        ( 0x0403, 0x6001, None), # VID=FTDI, PID=USB to serial
        )
    usbids=usbids_straight+usbids_usbtoserial
    
    # which device classes we are.  not we are not modem!
    deviceclasses=("serial",)

    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': 120, 'height': 98, 'format': "BMP"}))
    imagetargets.update(common.getkv(parentprofile.stockimagetargets, "pictureid",
                                      {'width': 120, 'height': 98, 'format': "BMP"}))
    imagetargets.update(common.getkv(parentprofile.stockimagetargets, "fullscreen",
                                      {'width': 120, 'height': 133, 'format': "BMP"}))

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


    def _getgroup(self, name, groups):
        for key in groups:
            if groups[key]['name']==name:
                return key,groups[key]
        return None,None
        

    def normalisegroups(self, helper, data):
        "Assigns groups based on category data"

        pad=[]
        keys=data['groups'].keys()
        keys.sort()
        for k in keys:
                if k: # ignore key 0 which is 'No Group'
                    name=data['groups'][k]['name']
                    pad.append(name)

        groups=helper.getmostpopularcategories(10, data['phonebook'], ["No Group"], 22, pad)

        # alpha sort
        groups.sort()

        # newgroups
        newgroups={}

        # put in No group
        newgroups[0]={'name': 'No Group', 'icon': 0}

        # populate
        for name in groups:
            # existing entries remain unchanged
            if name=="No Group": continue
            key,value=self._getgroup(name, data['groups'])
            if key is not None and key!=0:
                newgroups[key]=value
        # new entries get whatever numbers are free
        for name in groups:
            key,value=self._getgroup(name, newgroups)
            if key is None:
                for key in range(1,100000):
                    if key not in newgroups:
                        newgroups[key]={'name': name, 'icon': 1}
                        break
                       
        # yay, done
        if data['groups']!=newgroups:
            data['groups']=newgroups
            data['rebootphone']=True
            
    def convertphonebooktophone(self, helper, data):
        """Converts the data to what will be used by the phone

        @param data: contains the dict returned by getfundamentals
                     as well as where the results go"""
        results={}

        speeds={}

        self.normalisegroups(helper, data)

        for pbentry in data['phonebook']:
            if len(results)==self.protocolclass.NUMPHONEBOOKENTRIES:
                break
            e={} # entry out
            entry=data['phonebook'][pbentry] # entry in
            try:
                # serials
                serial1=helper.getserial(entry.get('serials', []), self.serialsname, data['uniqueserial'], 'serial1', 0)
                serial2=helper.getserial(entry.get('serials', []), self.serialsname, data['uniqueserial'], 'serial2', serial1)

                e['serial1']=serial1
                e['serial2']=serial2
                for ss in entry["serials"]:
                    if ss["sourcetype"]=="bitpim":
                        e['bitpimserial']=ss
                assert e['bitpimserial']

                # name
                e['name']=helper.getfullname(entry.get('names', []),1,1,22)[0]

                # categories/groups
                cat=helper.makeone(helper.getcategory(entry.get('categories', []),0,1,22), None)
                if cat is None:
                    e['group']=0
                else:
                    key,value=self._getgroup(cat, data['groups'])
                    if key is not None:
                        e['group']=key
                    else:
                        # sorry no space for this category
                        e['group']=0

                # email addresses
                emails=helper.getemails(entry.get('emails', []) ,0,self.protocolclass.NUMEMAILS,48)
                e['emails']=helper.filllist(emails, self.protocolclass.NUMEMAILS, "")

                # url
                e['url']=helper.makeone(helper.geturls(entry.get('urls', []), 0,1,48), "")

                # memo (-1 is to leave space for null terminator - not all software puts it in, but we do)
                e['memo']=helper.makeone(helper.getmemos(entry.get('memos', []), 0, 1, self.protocolclass.MEMOLENGTH-1), "")

                # phone numbers
                # there must be at least one email address or phonenumber
                minnumbers=1
                if len(emails): minnumbers=0
                numbers=helper.getnumbers(entry.get('numbers', []),minnumbers,self.protocolclass.NUMPHONENUMBERS)
                e['numbertypes']=[]
                e['numbers']=[]
                for numindex in range(len(numbers)):
                    num=numbers[numindex]
                    # deal with type
                    b4=len(e['numbertypes'])
                    type=num['type']
                    for i,t in enumerate(self.protocolclass.numbertypetab):
                        if type==t:
                            # some voodoo to ensure the second home becomes home2
                            if i in e['numbertypes'] and t[-1]!='2':
                                type+='2'
                                continue
                            e['numbertypes'].append(i)
                            break
                        if t=='none': # conveniently last entry
                            e['numbertypes'].append(i)
                            break
                    if len(e['numbertypes'])==b4:
                        # we couldn't find a type for the number
                        continue 
                    # deal with number
                    number=phonize(num['number'])
                    if len(number)==0:
                        # no actual digits in the number
                        continue
                    if len(number)>48: # get this number from somewhere sensible
                        # ::TODO:: number is too long and we have to either truncate it or ignore it?
                        number=number[:48] # truncate for moment
                    e['numbers'].append(number)
                    # deal with speed dial
                    sd=num.get("speeddial", -1)
                    if self.protocolclass.NUMSPEEDDIALS:
                        if sd>=self.protocolclass.FIRSTSPEEDDIAL and sd<=self.protocolclass.LASTSPEEDDIAL:
                            speeds[sd]=(e['bitpimserial'], numindex)

                e['numbertypes']=helper.filllist(e['numbertypes'], 5, 0)
                e['numbers']=helper.filllist(e['numbers'], 5, "")

                # ringtones, wallpaper
                e['ringtone']=helper.getringtone(entry.get('ringtones', []), 'call', None)
                e['msgringtone']=helper.getringtone(entry.get('ringtones', []), 'message', None)
                e['wallpaper']=helper.getwallpaper(entry.get('wallpapers', []), 'call', None)

                # flags
                e['secret']=helper.getflag(entry.get('flags',[]), 'secret', False)

                results[pbentry]=e
                
            except helper.ConversionFailed:
                continue

        if self.protocolclass.NUMSPEEDDIALS:
            data['speeddials']=speeds
        data['phonebook']=results
        return data

    _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
        #('sms', 'read', None),
        ('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'),
        )

    def QueryAudio(self, origin, currentextension, afi):
        # we don't modify any of these
        if afi.format in ("MIDI", "QCP", "PMD"):
            return currentextension, afi
        # examine mp3
        if afi.format=="MP3":
            if afi.channels==1 and 8<=afi.bitrate<=64 and 16000<=afi.samplerate<=22050:
                return currentextension, afi
        # convert it
        return ("mp3", fileinfo.AudioFileInfo(afi, **{'format': 'MP3', 'channels': 1, 'bitrate': 32, 'samplerate': 22050}))
p_lgvx4400.p (text/plain, 4.5 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: p_lgvx4400.p,v 1.20 2005/02/07 05:41:57 rogerb Exp $

%{

"""Various descriptions of data specific to LG VX4400"""

from prototypes import *

# Make all lg stuff available in this module as well
from p_lg import *

# We use LSB for all integer like fields
UINT=UINTlsb
BOOL=BOOLlsb

NUMSPEEDDIALS=100
FIRSTSPEEDDIAL=1
LASTSPEEDDIAL=99
SPEEDDIALOFFSET=0
NUMPHONEBOOKENTRIES=200
MAXCALENDARDESCRIPTION=38

NUMEMAILS=3
NUMPHONENUMBERS=5

NORINGTONE=0
NOMSGRINGTONE=0
NOWALLPAPER=0

MEMOLENGTH=33

numbertypetab=( 'home', 'home2', 'office', 'office2', 'cell', 'cell2',
                    'pager', 'fax', 'fax2', 'none' )

%}

PACKET speeddial:
    1 UINT {'default': 0xff} +entry
    1 UINT {'default': 0xff} +number

PACKET speeddials:
    * LIST {'length': NUMSPEEDDIALS, 'elementclass': speeddial} +speeddials

PACKET pbreadentryresponse:
    "Results of reading one entry"
    *  pbheader header
    *  pbentry  entry

PACKET pbupdateentryrequest:
    * pbheader {'command': 0x04, 'flag': 0x01} +header
    * pbentry entry

PACKET pbappendentryrequest:
    * pbheader {'command': 0x03, 'flag': 0x01} +header
    * pbentry entry

# All STRINGS have raiseonterminatedread as False since the phone does
# occassionally leave out the terminator byte
# Note if you change the length of any of these fields, you also
# need to modify com_lgvx4400 to give a different truncateat parameter
# in the convertphonebooktophone method
PACKET pbentry:
    4  UINT serial1
    2  UINT {'constant': 0x0202} +entrysize
    4  UINT serial2
    2  UINT entrynumber 
    23 STRING {'raiseonunterminatedread': False} name
    2  UINT group
    *  LIST {'length': NUMEMAILS} +emails:
        49 STRING {'raiseonunterminatedread': False} email
    49 STRING {'raiseonunterminatedread': False} url
    1  UINT ringtone                                     "ringtone index for a call"
    1  UINT msgringtone                                  "ringtone index for a text message"
    1  BOOL secret
    * STRING {'raiseonunterminatedread': False, 'sizeinbytes': MEMOLENGTH} memo
    1  UINT wallpaper
    * LIST {'length': NUMPHONENUMBERS} +numbertypes:
        1 UINT numbertype
    * LIST {'length': NUMPHONENUMBERS} +numbers:
        49 STRING {'raiseonunterminatedread': False} number
    * UNKNOWN +unknown20c

PACKET pbgroup:
    "A single group"
    1 UINT icon
    23 STRING name

PACKET pbgroups:
    "Phonebook groups"
    * LIST {'elementclass': pbgroup} +groups

PACKET indexentry:
    2 UINT {'default': 0xffff} +index
    40 STRING {'default': ""} +name


PACKET indexfile:
    "Used for tracking wallpaper and ringtones"
    # A bit of a silly design again.  Entries with an index of 0xffff are
    # 'blank'.  Thus it is possible for numactiveitems and the actual
    # number of valid entries to be mismatched.
    P UINT {'constant': 30} maxitems
    2 UINT numactiveitems
    * LIST {'length': self.maxitems, 'elementclass': indexentry, 'createdefault': True} +items

###
### The calendar
###
#
#   The calendar consists of one file listing events and an exception
#   file that lists exceptions.  These exceptions suppress a particular
#   instance of a repeated event.  For example, if you setup something
#   to happen monthly, but changed the 1st february event, then the
#   schedule will contain the repeating event, and the 1st feb one,
#   and the suppresions/exceptions file will point to the repeating
#   event and suppress the 1st feb.
#   The phone uses the position within the file to give an event an id

PACKET scheduleexception:
    4 UINT pos "Refers to event id (position in schedule file) that this suppresses"
    1 UINT day
    1 UINT month
    2 UINT year

PACKET scheduleexceptionfile:
    * LIST {'elementclass': scheduleexception} +items

PACKET scheduleevent:
    4 UINT pos "position within file, used as an event id"
    4 LGCALDATE start
    4 LGCALDATE end
    1 UINT repeat
    3 UINT daybitmap  "which days a weekly repeat event happens on"
    1 UINT alarmminutes  "a value of 100 indicates not set"
    1 UINT alarmhours    "a value of 100 indicates not set"
    1 UINT changeserial
    1 UINT snoozedelay   "in minutes"
    1 UINT ringtone
    39 STRING {'raiseonunterminatedread': False} description


PACKET schedulefile:
    2 UINT numactiveitems
    * LIST {'elementclass': scheduleevent} +events
p_lgvx8100.p (text/plain, 7.7 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.
###
### $Id: p_lgvx8100.p,v 1.1 2005/08/01 06:08:52 rogerb Exp $

%{

"""Various descriptions of data specific to LG VX8000"""

from prototypes import *

# Make all lg stuff available in this module as well
from p_lg import *

# we are the same as lgvx7000 except as noted
# below
from p_lgvx7000 import *

# We use LSB for all integer like fields
UINT=UINTlsb
BOOL=BOOLlsb

# vx8100 uses a 1 based index for speed dials instead of 0 like the vx4400
SPEEDDIALOFFSET=1
MAXCALENDARDESCRIPTION=32


class LGCALREPEAT(UINTlsb):
    def __init__(self, *args, **kwargs):
        """A 32-bit bitmapped value used to store repeat info for events in the LG calendar"""
        super(LGCALREPEAT,self).__init__(*args, **kwargs)
        
        # The meaning of the bits in this field
        # MSB                          LSB
        #  3         2         1         
        # 10987654321098765432109876543210
        #                              210  repeat_type
        #                            0      exceptions, set to 1 if there are exceptions
        #                     6543210       dow_weekly (weekly repeat type)
        #                         210       dow (monthly repeat type)
        #             543210                interval
        #               3210                month_index
        #  543210                           day_index    

        # repeat_type: 0=none, 1=daily, 2=weekly, 3=monthly, 4=yearly, 5=weekdays, 6=XthDayEachMonth(e.g. 3rd Friday each month)
        # dow_weekly: Weekly repeat type only. Identical to bpcalender dow bits, multiple selections allowed(Bit0=sun,Bit1=mon,Bit2=tue,Bit3=wed,Bit4=thur,Bit5=fri,Bit6=sat)  
        # dow_monthly: Monthly repeat type 6 only. (0=sun,1=mon,2=tue,3=wed,4=thur,5=fri,6=sat)
        # interval: repeat interval, eg. every 1 week, 2 weeks 4 weeks etc. Also be used for months, but bp does not support this.
        # month_index: For type 4 this is the month the event starts in
        # day_index: For type 6 this represents the number of the day that is the repeat, e.g. "2"nd tuesday
        #            For type 3&4 this is the day of the month that the repeat occurs, usually the same as the start date.
        #            bp does not support this not being the support date

        dict={'sizeinbytes': 4}
        dict.update(kwargs)

        if self._ismostderived(LGCALREPEAT):
            self._update(args,kwargs)

    def _update(self, args, kwargs):
        for k in 'constant', 'default', 'value':
            if kwargs.has_key(k):
                kwargs[k]=self._converttoint(kwargs[k])
        if len(args)==0:
            pass
        elif len(args)==1:
            args=(self._converttoint(args[0]),)
        else:
            raise TypeError("expected (type, dow, interval) as arg")

        super(LGCALREPEAT,self)._update(args, kwargs) # we want the args
        self._complainaboutunusedargs(LGCALDATE,kwargs)
        assert self._sizeinbytes==4

    def getvalue(self):
        val=super(LGCALREPEAT,self).getvalue()
        # get repeat type
        type=val&0x7 # 3 bits
        val>>=4
        exceptions=val&0x1
        val>>=1
        #get day of week, only valid for some repeat types
        #format of data is also different for different repeat types
        if type==6: # for monthly repeats
            dow=1<<(val&3) #day of month, valid for monthly repeat types, need to convert to bitpim format
        elif type==2: #weekly 
            dow=val&0x7f # 7 bits, already matched bpcalender format
        else:
            dow=0
        # get interval
        if type==6:
            val>>=20
            interval=val&0x1f # day_index
        else:
            val>>=9
            interval=val&0x3f
        return (type, dow, interval, exceptions)

    _caldomvalues={
        0x01: 0x0, #sun
        0x02: 0x1, #mon
        0x04: 0x2, #tue
        0x08: 0x3, #wed
        0x10: 0x4, #thur
        0x20: 0x5, #fri
        0x40: 0x6  #sat
        }
        
    def _converttoint(self, repeat):
        assert len(repeat)==4
        type,dow,interval,exceptions=repeat
        val=0
        # construct bitmapped value for repeat
        # look for weekday type
        val=interval
        if type==6 or type==3:
            val<<=11
            val|=1 # force monthly interval to 1
        if type==4: #yearly
            val<<=11
            val|=dow
        val<<=9
        if type==2:
            val|=dow
        elif type==6:
            val|=self._caldomvalues[dow]
        val<<=1
        val|=exceptions
        val<<=4
        val|=type
        return val

%}
    
PACKET indexentry:
    2 UINT index
    2 UINT type
    # they shortened this from 84 chars in the vx7000
    68 STRING filename  "includes full pathname"
    4 UINT {'default': 0} +date "i think this is bitfield of the date"
    4 UINT dunno

PACKET indexfile:
    "Used for tracking wallpaper and ringtones"
    * LIST {'elementclass': indexentry, 'createdefault': True} +items

PACKET pbgroup:
    "A single group"
    23 STRING name

PACKET pbgroups:
    "Phonebook groups"
    * LIST {'elementclass': pbgroup} +groups

###
### The calendar
###
#
#   The calendar consists of one file listing events and an exception
#   file that lists exceptions.  These exceptions suppress a particular
#   instance of a repeated event.  For example, if you setup something
#   to happen monthly, but changed the 1st february event, then the
#   schedule will contain the repeating event, and the 1st feb one,
#   and the suppresions/exceptions file will point to the repeating
#   event and suppress the 1st feb.
#   The phone uses the position within the file to give an event an id

PACKET scheduleexception:
    4 UINT pos "Refers to event id (position in schedule file) that this suppresses"
    1 UINT day
    1 UINT month
    2 UINT year

PACKET scheduleexceptionfile:
    * LIST {'elementclass': scheduleexception} +items

PACKET scheduleevent:
    4 UINT pos "position within file, used as an event id"
    33 STRING {'raiseonunterminatedread': False} description
    4 LGCALDATE start
    4 LGCALDATE end
    4 LGCALREPEAT repeat # complicated bit mapped field
    1 UINT alarmindex_vibrate #LSBit of this set vibrate ON(0)/OFF(1), the 7 MSBits are the alarm index
                              #the alarmindex is the index into the amount of time in advance of the 
                              #event to notify the user. It is directly related to the alarmminutes 
                              #and alarmhours below, valid values are
                              # 8=2days, 7=1day, 6=2hours, 5=1hour, 4=15mins, 3=10mins, 2=5mins, 1=0mins, 0=NoAlarm
    1 UINT ringtone
    1 UINT unknown1
    1 UINT alarmminutes  "a value of 0xFF indicates not set"
    1 UINT alarmhours    "a value of 0xFF indicates not set"
    1 UINT unknown2


PACKET schedulefile:
    2 UINT numactiveitems
    * LIST {'elementclass': scheduleevent} +events

PACKET call:
    4 UINT GPStime #no. of seconds since 0h 1-6-80, based off local time.
    4 UINT unknown2 # different for each call
    4 UINT duration #seconds, not certain about length of this field
    49 STRING {'raiseonunterminatedread': False} number
    36 STRING {'raiseonunterminatedread': False} name
    2 UINT numberlength # length of phone number
    1 UINT pbnumbertype # 1=cell, 2=home, 3=office, 4=cell2, 5=fax, 6=vmail, 0xFF=not in phone book
    3 UINT unknown2 # always seems to be 0
    2 UINT pbentrynum #entry number in phonebook

PACKET callhistory:
    4 UINT numcalls
    1 UINT unknown1
    * LIST {'elementclass': call} +calls
phone-lgvx8100.htd (text/plain, 560 B)
#define _HELP_NAVTREE_ID 89
define _HELP_NAVTREE_ID 85
#include "pagestart.h"

<h2>What works</h2>

<p>Phonebook, wallpaper (including camera), ringers, 
filesystem, calender.  (<b>Note:</b> Videos are currently ignored.)

<h2>FAQs and support</h2>

<p>If you know of a good support group, please let us know so we can add details here.

<h2>Contents</h2>

<!-- CONTENTS BEGIN -->
BEGIN_TOC
    TOC_0
    TOCITEM_0(Cables,phone-lgvx8100-cables.htm)
    TOCITEM_0(Notes,phone-lgvx8100-notes.htm)
    ENDTOC_0
END_TOC
<!-- CONTENTS END -->

#include "pageend.h"
phone-lgvx8100-cables.htd (text/plain, 999 B)
#define _HELP_NAVTREE_ID 90
#include "pagestart.h"

<p>This phone supports USB charging cables.

<h3>RPI Wireless straight USB with charging</h3>

<p>You can also buy from e-bay and Verizon stores (although they are a bit expensive)
You can also try RS and other electronic stores. All testing done with USB cables.
Note that the VX6100, VX7000, VX8000 and VX8100 all use exactly the
same cable.
This phone also has Bluetooth (BT). I have heard that it is possible to use this to
connect BitPim to the phone if you have BT on your Computer instead of using a cable.
To use BT you need to confgure your phone.
Go into the main menu, press <OK>, then go to Settings and Tools, press <RIGHT ARROW> 
twice. Select PC Connection, press <9>, and then choose BT, press <3>. The phone should
now show up as a BT device on your Computer. I have not tried this, but you can try to
get help on a phone forum like URL(www.howardsforums.com).

<p align=center><img src="vx7000-cable.jpg">


#include "pageend.h"
phone-lgvx8100-notes.htd (text/plain, 951 B)
#define _HELP_NAVTREE_ID 91
#include "pagestart.h"

<p>There has been a report that this PitBim will crash if you have pager numbers in the phonebook
that you are trying to upload into the phone. Removing the pager entries resolves this issue.

<p>This phone returns the number of phonebook entries in an unreliable way to BitPim.  There is a
workaround in the BitPim code for it.  However the workaround does not work if the phonebook 
on the phone is completely empty.  To reliably read or write the phonebook with BitPim, you
must ensure there is at least one entry on the phone already.

<p>The videos taken by the phone use MPEG 4 for the video and Qualcomm's proprietary PureVoice
format for the audio.  Most media players have no problem with the video but won't know how
to do the audio.  Apple's QuickTime software (even on Windows) does understand the PureVoice
format so you can use it to listen to the video as well.

#include "pageend.h"
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.