Re: yet more vx8100 updates

"Joe Pham" <[email protected]>
Newsgroups gmane.comp.mobile.bitpim.devel
Message-ID <[email protected]>
>set the 'id' of the CallHistoryEntry, the 8100 reads so fast that using the time() func does not >produce unique IDs
AFAIK, you're the first one to implement this feature for any phone.  I updated call_history to make the id more precise, and let's see how fast is your 8100 :-)  Please give it a try and let me know.

-Joe Pham
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": `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()
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.