#
#  $Id: Database.py,v 1.7 1999/08/09 21:59:12 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!
#
"""Pyrite Databases.
"""

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

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

# Database Properties
#
# id-store-nulls - true if the db can store null ids
# id-auto-assign - true if the db auto-assigns ids when you send a null
# id-unique      - true if non-null ids are guaranteed to be unique
# id-replace     - true if inserting a non-null id replaces record with same
#                  id (if not true, then raises an exception!)
# record-replace - whether or not a record can be replaced in place
# record-insert  - whether or not a record can be inserted at an index
# 
#

from Blocks import Record, AppBlock, Resource

class Database:
    def __init__(self, db, info, mode='rws', properties=()):
	self.__db = db
	self.info = info
	self.record_class = Record
	self.appblock_class = AppBlock
	self.resource_class = Resource
	self.properties = properties
	self.mode = mode
	self.is_resource = info.get('flagResource')
	
    # Miscellaneous APIs
    def has_property(self, p):
	return p in self.properties

    def new_record(self, index=0, id=0, attributes=0, category=0, *a, **kw):
	"""Return a new, blank record.
	"""
	return self.record_class('', index, id, attributes, category)

    def new_appblock(self, *a, **kw):
	"""Return a new, empty appinfo block.
	"""
	return self.appblock_class()

    def new_resource(self, type='    ', id=0, *a, **kw):
	"""Return a new, empty resource.
	"""
	return self.resource_class('', type, id)
    
    def classify_record(self, *a, **kw):
	"""Classify a record.

	The actual parameters this is called with are:
	    (raw, index, id, attributes, category)
	and it should, from this, determine what kind of record it
	is and wrap it in the proper class.
	"""
	return apply(self.record_class, a, kw)

    def classify_resource(self, *a, **kw):
	"""Classify a resource.

	Like classify_record, but with parameters (raw, type, id).
	"""
	return apply(self.resource_class, a, kw)
    
    # Pythonish API
    def __len__(self):
	return self.__db.getRecords()
    
    def __getitem__(self, i):
	if self.is_resource:
	    r = self.__db.getResource(i)
	    if r is None: raise IndexError, i
	    return apply(self.classify_resource, r)
	else:
	    r = self.__db.getRecord(i)
	    if r is None: raise IndexError, i
	    return apply(self.classify_record, r)

    def append(self, rec):
	if self.is_resource:
	    try:
		return self.__db.addResource(rec.type, rec.id, rec.pack())
	    except AttributeError:
		return self.__db.setResource(rec.type, rec.id, rec.pack())
	else:
	    try:
		return self.__db.addRecord(rec.attributes(), rec.id, rec.category,
					   rec.pack())
	    except AttributeError:
		id = self.__db.setRecord(rec.attributes(), rec.id, rec.category,
					 rec.pack())
		rec.id = id
		return id    

    # non-pythonisms
    def get_id(self, a, b=None):
	if self.is_resource:
	    r = self.__db.getResourceByID(a, b)
	    if r is None: return None
	    return apply(self.classify_resource, r)
	else:
	    r = self.__db.getRecordByID(a)
	    if r is None: return None
	    return apply(self.classify_record, r)

    def delete(self, a, b=None):
	if self.is_resource:
	    return self.__db.deleteResource(a, b)
	else:
	    return self.__db.deleteRecord(a)
	
    def delete_all(self):
	if self.is_resource:
	    return self.__db.deleteResources()
	else:
	    return self.__db.deleteRecords()

    def get_appblock(self):
	r = self.__db.getAppBlock()
	if r is not None:
	    return self.appblock_class(r)
	else:
	    return None

    def set_appblock(self, blk):
	return self.__db.setAppBlock(blk.pack())

    def get_sortblock(self):
	r = self.__db.getSortBlock()
	if r is not None:
	    return self.sortblock_class(r)
	else:
	    return None

    def set_sortblock(self, blk):
	return self.__db.setSortBlock(blk.pack())

    def category_move(self, frm, to):
	return self.__db.moveCategory(frm, to)

    def category_delete(self, cat):
	return self.__db.deleteCategory(cat)

    def get_preference(self, id=0, backup=1):
	raw, creator, id, version, backup = self.__db.getPref(self.info.creator,
							      id, backup)
	return PrefBlock(raw, creator, id, version, backup)

    def set_preference(self, pref):
	return self.__db.setPref(pref.creator, pref.id, pref.backup,
				 pref.version, pref.pack())

    def reset_flags(self):
	"""Clear all record dirty flags; set last sync time to now."""
	return self.__db.resetFlags()

    def purge_deleted(self):
	"""Purge deleted and archived records."""
	return self.__db.purge()

    def next_reset(self):
	return self.__db.resetNext()

    def next_record(self, cat):
	r = self.__db.getNextRecord(cat)
	if r is None: return None
	return apply(self.classify_record, r)

    def next_modified(self, cat=-1):
	r = self.__db.getNextModRecord(cat)
	if r is None: return None
	return apply(self.classify_record, r)

    def ids(self, sort=0):
	return self.__db.getRecordIDs(sort)
    
    def close(self):
	return self.__db.close()

    # Helpers
    def __getslice__(self, low, high):
	return Slice(self, low, high)
    
    def Category(self, cat):
	self.next_reset()
	return CategoryIterator(cat, self.next_record)

    def ModifiedRecords(self, cat=-1):
	self.next_reset()
	return CategoryIterator(cat, self.next_modified)

class Slice:
    """A generic slice, which serves mainly to permit Python to not
    have to retrieve a bunch of records at once.  Doesn't support
    everything a real slice does, but should allow iteration and
    retrieval over a portion of a database."""
    def __init__(self, list, low, high):
	self.list = list
	self.low = low
	self.high = high
	self.len = high-low
    def __len__(self): return self.len
    def __getitem__(self, n):
	if n >= self.len: raise IndexError
	return self.list[n+self.low]
    
class CategoryIterator:
    """An iterator which returns each record in a category using
    getNextModRecord or getNextRecord."""
    def __init__(self, cat, func):
	self.cat = cat
	self.func = func

    def __getitem__(self, n):
	return self.func(self.cat)
