#
#  $Id: pdbfile.py,v 1.7 1999/08/09 21:59:13 rob Exp $
#
#  Copyright 1998-1999 Rob Tillotson <robt@debian.org>
#  All Rights Reserved
#
#  Permission to use, copy, modify, and distribute this software and
#  its documentation for any purpose and without fee or royalty is
#  hereby granted, provided that the above copyright notice appear in
#  all copies and that both the copyright notice and this permission
#  notice appear in supporting documentation or portions thereof,
#  including modifications, that you you make.
#
#  THE AUTHOR ROB TILLOTSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
#  THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
#  AND FITNESS.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
#  SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
#  RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
#  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
#  CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE!
#
"""PDB file utilities.

  This module contains various utility functions which may be useful
  to programs -- installers, conduits, etc. -- that deal with PalmOS
  databases on the desktop.
"""

__version__ = '$Id: pdbfile.py,v 1.7 1999/08/09 21:59:13 rob Exp $'

__copyright__ = 'Copyright 1998-1999 Rob Tillotson <robt@debian.org>'


import Pyrite

import sys, os, stat, struct

PI_HDR_SIZE = 78
PILOT_TIME_DELTA = 2082844800

dlpDBFlagResource = 0x0001
dlpDBFlagReadOnly = 0x0002
dlpDBFlagAppInfoDirty = 0x0004
dlpDBFlagBackup = 0x0008
dlpDBFlagOpen = 0x8000
# 2.x
dlpDBFlagNewer = 0x0010
dlpDBFlagReset = 0x0020
#
dlpDBMiscFlagExcludeFromSync = 0x80

def flags_to_info(flags):
    """Convert a numeric flag word to a dictionary.
    The result is suitable for merging into an info structure
    with 'update'.
    """
    i = {'flagReset': flags & dlpDBFlagReset,
	 'flagResource': flags & dlpDBFlagResource,
	 'flagNewer': flags & dlpDBFlagNewer,
	 'flagExcludeFromSync': flags & dlpDBMiscFlagExcludeFromSync,
	 'flagAppInfoDirty': flags & dlpDBFlagAppInfoDirty,
	 'flagReadOnly': flags & dlpDBFlagReadOnly,
	 'flagBackup': flags & dlpDBFlagBackup,
	 'flagOpen': flags & dlpDBFlagOpen,
	 }
    return i

def info_to_flags(i):
    """Extract a numeric flag word from an info structure.
    """
    flags = 0
    if i.get('flagReset'): flags = flags | dlpDBFlagReset
    if i.get('flagResource'): flags = flags | dlpDBFlagResource
    if i.get('flagNewer'): flags = flags | dlpDBFlagNewer
    if i.get('flagExcludeFromSync'): flags = flags | dlpDBMiscFlagExcludeFromSync
    if i.get('flagAppInfoDirty'): flags = flags | dlpDBFlagAppInfoDirty
    if i.get('flagBackup'): flags = flags | dlpDBFlagBackup
    if i.get('flagOpen'): flags = flags | dlpDBFlagOpen
    return flags

def null_terminated(s):
    for x in range(0, len(s)):
	if s[x] == '\000': return s[:x]
    return s

def info(name):
    """Read the header of a PalmOS database file.  Although there is
    no way to conclusively determine that a file contains a valid
    database, this function does a few simple checks and returns None
    if the file is invalid.  Pilot-link also does this on open, but
    since it prints error messages to stderr it is inappropriate to
    use it to test a bunch of files in a directory."""
    
    f = open(name, 'rb')
    hstr = f.read(PI_HDR_SIZE)
    if not hstr or len(hstr) < PI_HDR_SIZE: return None

    (name, flags, ver, ctime, mtime, btime, mnum, appinfo, sortinfo,
     type, creator, uid, nextrec, numrec) \
     = struct.unpack('>32shhLLLlll4s4sllh', hstr)

    # "extended format not supported"
    if nextrec: return None

    # "bad header"
    if appinfo < 0 or sortinfo < 0 or numrec < 0: return None

    i = {'name': null_terminated(name),
	 'type': type,
	 'creator': creator,
	 'createDate': ctime - PILOT_TIME_DELTA,
	 'modifyDate': mtime - PILOT_TIME_DELTA,
	 'backupDate': btime - PILOT_TIME_DELTA,
	 'modnum': 3,
	 'version': ver,
	 'flagReset': flags & dlpDBFlagReset,
	 'flagResource': flags & dlpDBFlagResource,
	 'flagNewer': flags & dlpDBFlagNewer,
	 'flagExcludeFromSync': flags & dlpDBMiscFlagExcludeFromSync,
	 'flagAppInfoDirty': flags & dlpDBFlagAppInfoDirty,
	 'flagReadOnly': flags & dlpDBFlagReadOnly,
	 'flagBackup': flags & dlpDBFlagBackup,
	 'flagOpen': flags & dlpDBFlagOpen,
	 'more': 0,
	 'index': 0
	 }

    return i

    
def x_info(name):
    """Return the info structure of a PalmOS database file."""

    f = Pyrite.openFile(name, typemapper=None)
    return f.info

def listdir(path):
    """Scan the specified path for PalmOS database files and return
    a list of tuples of (name, info) for each database found.
    This function may be expanded in the future to handle zipfiles,
    gzipped databases, etc."""

    d = os.listdir(path)
    l = []
    
    for f in d:
	fn = os.path.join(path, f)

	# any exception at all means that the file isn't worth reporting
	try:
	    st = os.stat(fn)

	    if stat.S_ISREG(st[stat.ST_MODE]):
		i = info(fn)
		if i:
		    l.append( (f, i) )
	except:
	    pass

    return l

def listsubdirs(path):
    """List all subdirectories of the current directory."""
    # doesn't really belong here
    d = os.listdir(path)
    l = []

    for f in d:
	fn = os.path.join(path, f)
	try:
	    st = os.stat(fn)
	    if stat.S_ISDIR(st[stat.ST_MODE]): l.append(f)
	except:
	    pass
    return l

		
