#!/usr/bin/env python
# GPixPod - the free and open source way to manage photos on your POD!
# Copyright (C) 2006 Flavio Gargiulo (FLAGAR.com)
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
import os, sys, shutil, time
from utils import *
from imgconvs import *
from struct import *
 
class MH:
    """ Process the MH database """
    formats = {'mhfd':'4s16I64x', 'mhsd':'4s3I80x', 'mhli':'4s2I80x', 'mhii':'4s12I100x', 'mhod':'4s2I1H2B8x',
               'mhni':'4s6I2h2H40x', 'mhla':'4s2I80x', 'mhba':'4s15I84x', 'mhia':'4s4I20x', 'mhlf':'4s2I80x', 'mhif':'4s5I100x'}
    lengths = dict(map(lambda(x): (x[0], calcsize(x[1])), formats.items()))
    latest = {'mhfd':0, 'mhsd':0, 'mhli':0, 'mhii':0, 'mhoc':0, 'mhni':0, 'mhod':0, 'mhos':0, 'mhla':0, 'mhba':0, 'mhia':0, 'mhlf':0, 'mhif':0}
    parents = {'mhfd':'mhfd', 'mhsd':'mhfd', 'mhli':'mhsd', 'mhii':'mhli', 'mhoe':'mhii', 'mhoc':'mhii', 'mhni':'mhoc', 'mhod':'mhni', 'mhos':'mhod',
               'mhla':'mhsd', 'mhba':'mhla', 'mhob':'mhba', 'mhoa':'mhob', 'mhia':'mhba', 'mhlf':'mhsd', 'mhif':'mhlf'}
    # MHOE: container MHOD for full res.; MHOC: container MHOD for thumb; MHOD: string MHOD type-3; MHOS: string MHOD type-3 subheader;
    # MHOB: string MHOD type-1; MHOA: string MHOD type-1 subheader.
    #thumbdbsizes = ((712, 480), (320, 240), (123, 88), (53, 41)) MOVED TO __INIT__
    #thumbsizes = ((720, 480), (320, 240), (130, 88), (50, 41))  MOVED TO __INIT__
    #thumbdims = tuple(map(lambda(x): x[0]*x[1]*2, thumbsizes))  MOVED TO __INIT__
    #thumbfnames = (1019, 1024, 1015, 1036)  # Default thumbnails filename parts
    removed_photos_ids = []
    new_photos_added = []
    thumbs_extracted = False
    utilities_path = './utilities/'
    
    def __init__(self, dir='/mnt/ipod/Photos/Photo Database'):
        """ Try to open the Photo Database file """
        # Figuring out the full filename and main path
	if dir.find('Photo Database') == -1:
            self.dbname = os.path.join(dir, 'Photo Database')
	    self.dbdir = dir
	else:
	    self.dbname = dir
	    self.dbdir = dir.replace('Photo Database', '')
        # Detecting iPod model and setting thumbnail infos
        self.ipod_model = getIpodModel(self.dbdir.replace('Photos', ''))
        if self.ipod_model == '5G':  # Supported for sure: developer's one
            self.thumbdbsizes = ((712, 480), (320, 240), (123, 88), (53, 41))
            self.thumbsizes = ((720, 480), (320, 240), (130, 88), (50, 41))
	    self.thumbfnames = (1019, 1024, 1015, 1036)
        elif self.ipod_model == 'Photo': # Tested and verified
	    self.thumbdbsizes = ((712, 452), (235, 146), (130, 87), (43, 30))
	    self.thumbsizes = ((720, 480), (220, 176), (130, 88), (42, 30))
	    self.thumbfnames = (1019, 1013, 1015, 1009)
	elif self.ipod_model == 'Color': # Guessed (same as Photo?)
            self.thumbdbsizes = ((712, 452), (235, 146), (130, 87), (43, 30))
            self.thumbsizes = ((720, 480), (220, 176), (130, 88), (42, 30))
	    self.thumbfnames = (1019, 1013, 1015, 1009)
        elif self.ipod_model == 'Nano':  # Tested and verified
            self.thumbdbsizes = ((176, 132), (45, 37))
            self.thumbsizes = ((176, 132), (42, 37))
	    self.thumbfnames = (1023, 1032)
        else:  # Using iPod 5G values as default for other, unsupported models. The GUI will warn.
            self.thumbdbsizes = ((712, 480), (320, 240), (123, 88), (53, 41))
            self.thumbsizes = ((720, 480), (320, 240), (130, 88), (50, 41))
	    self.thumbfnames = (1019, 1024, 1015, 1036)
        self.thumbdims = tuple(map(lambda(x): x[0]*x[1]*2, self.thumbsizes))              
        # Photo Database opening
	try:
	    self.db = open(self.dbname, 'rb')
	except IOError:
	    print "Directory does not contain a Photo Database file"
	    print "A new one will be created based on the path specified"
	    # FromScratch opening should be modelled better as in exceptions as in questions before overwriting
	    self._FromScratch()
	    if not os.path.isdir(self.dbdir):
	        os.makedirs(self.dbdir)
	    # Creating thumbnails files if they don't exist yet
	    if not os.path.isdir(os.path.join(self.dbdir, 'Thumbs')):
	        os.mkdir(os.path.join(self.dbdir, 'Thumbs'))
	    for x in self.thumbfnames:
                t = open(os.path.join(self.dbdir, 'Thumbs', 'F%s_1.ithmb' % x), 'ab')
		t.close()
	    self._SaveDB()
	    self.db = open(self.dbname, 'rb')
        self._FirstScan()
	self._ThumbsExtract()
	self.WriteDatabaseLog()
   
    def _FromScratch(self):
        """ Define a new self.dblines from scratch to generate a new empty database file """
        # Template for an iPod video 5G
	self.dblines = []
	self.dblines.append(['mhfd', ['mhfd', 132, 1252, 0, 1, 3, 0, 100, 0, 0, 0, 0, 2, 0, 0, 0, 0], 0])
	self.dblines.append(['mhsd', ['mhsd', 96, 188, 1], 0])
	self.dblines.append(['mhli', ['mhli', 92, 0], 1])
	self.dblines.append(['mhsd', ['mhsd', 96, 380, 2], 0])
	self.dblines.append(['mhla', ['mhla', 92, 1], 3])
	self.dblines.append(['mhba', ['mhba', 148, 192, 1, 0, 100, 0, 65536, 0, 0, 0, 0, 0, 0, 0, 100], 4])
	self.dblines.append(['mhob', ['mhod', 24, 44, 1, 0, 1], 5])
	self.dblines.append(['mhoa', [7, 1, 0, 'Library'.encode('utf-8')], 6])
	self.dblines.append(['mhsd', ['mhsd', 96, 684, 3], 0])
	self.dblines.append(['mhlf', ['mhlf', 92, 4], 5])
	self.dblines.append(['mhif', ['mhif', 124, 124, 0, 1019, 691200], 6])
	self.dblines.append(['mhif', ['mhif', 124, 124, 0, 1015, 22880], 6])
	self.dblines.append(['mhif', ['mhif', 124, 124, 0, 1024, 153600], 6])
	self.dblines.append(['mhif', ['mhif', 124, 124, 0, 1036, 4100], 6])
	# Modifying template for iPod Photo/Color
	if self.ipod_model == 'Photo' or self.ipod_model == 'Color':
	    self.dblines[-1][1][5] = 2520  # Modifying dimension of the 42x30 thumbnail
	    self.dblines[-2][1][5] = 77440  # Modifying dimension of the 220x176 thumbnail
	    # Updating thumbnails filenames parts
	    self.dblines[-4][1][4] = self.thumbfnames[0]
	    self.dblines[-3][1][4] = self.thumbfnames[2]
	    self.dblines[-2][1][4] = self.thumbfnames[1]
	    self.dblines[-1][1][4] = self.thumbfnames[3]
	# Modifying template for iPod Nano
	elif self.ipod_model == 'Nano':
	    del self.dblines[-4:-2]  # deleting missing thumbnails types
	    # Updating thumbnails filename parts
	    self.dblines[-2][1][4] = self.thumbfnames[0]
	    self.dblines[-1][1][4] = self.thumbfnames[1]
	    self.dblines[-1][1][5] = 3108  # Modifying dimension of the 42x37 thumbnail
	    self.dblines[-2][1][5] = 46464  # Modifying dimension of the 176x132 thumbnail
	    self.dblines[-3][1][2] = 2  # Modifying MHLF indicating only 2 thumbnails types
	    self.dblines[-4][1][2] -= 248  # Updating parent MHSD total length, after having deleted 2 MHIF
	    self.dblines[0][1][2] -= 248  # Updating MHFD, too
   
    def _FirstScan(self):
        """ Scan the database file """
        self.db.seek(0, 2); dbsize = self.db.tell()
	self.db.seek(0); self.dblines = []
        while self.db.tell() < dbsize:
	    dbline = unpack('4s', self.db.read(4))[0]
            self.db.seek(-4, 1)
	    self.dblines.append([dbline, list(unpack(self.formats[dbline], self.db.read(self.lengths[dbline]))), self.latest[self.parents[dbline]]])
	    if dbline == 'mhod':
	        mhod_type = self.dblines[-1][1][3]
	        if mhod_type == 5:
		    self.dblines[-1][0] = 'mhoe' 
		elif mhod_type == 2:
		    self.dblines[-1][0] = 'mhoc'
		elif mhod_type == 1:
		    self.dblines[-1][0] = 'mhob'
		    mhodsubname = 'mhoa'
		elif mhod_type == 3:
		    mhodsubname = 'mhos'
		mhod_name = self.dblines[-1][0]
		self.dblines[-1][2] = self.latest[self.parents[mhod_name]]
		self.latest[mhod_name] = len(self.dblines)-1
                if mhod_name == 'mhod' or mhod_name == 'mhob':  # string MHOD
	            mhodslen = unpack('I', self.db.read(4))[0]; self.db.seek(-4, 1)
	            mhodsformat = '3I'+str(mhodslen)+'s'+str(self.dblines[-1][1][5])+'x'
	            self.dblines.append([mhodsubname, list(unpack(mhodsformat, self.db.read(calcsize(mhodsformat)))), self.latest[mhod_name]])
		    self.latest[mhodsubname] = len(self.dblines)-1
            else:
                self.latest[dbline] = len(self.dblines)-1
        self._RetrieveThumbnailsFilenameParts()
	self.db.close()
 
    def _RetrieveThumbnailsFilenameParts(self):
        """ Retrieve thumbnail filename parts (to name files in the Thumbs directory) """
	self.thumbfnames = []
	dblines_len = len(self.dblines)
	# iPod Photo, Color and 5G seem to have 4 different thumbnails types; iPod Nano seems to have just 2 types.
	for x in range(dblines_len-4, dblines_len):
	    if self.dblines[x][0] == 'mhif':
	        self.thumbfnames.append((self.dblines[x][1][5], self.dblines[x][1][4]))
	self.thumbfnames = dict(self.thumbfnames)
 
    def WriteDatabaseLog(self):
        """ Write a .gpixpod.log debug file in the Photos directory """
	logfile = open(os.path.join(self.dbdir, '.gpixpod.log'), 'w+')
	for dbline in self.dblines:
	    print >> logfile, dbline
	logfile.close()
 
    def _SaveDB(self, filename=None):
        """ Write all the changes back to the database file """
	if filename == None:
	    filename = self.dbname
	if filename == self.dbname and os.path.isfile(filename):  # if saving to the same file, creating backup
	    backup_filename = os.path.join(self.dbdir, 'Photo Database.gpixpodbak')
	    if os.path.isfile(backup_filename):
	        os.remove(backup_filename)
	    os.rename(self.dbname, backup_filename)
	self._PrepareToSave()
	db = open(filename, 'w+b')
	try:
	    for x in range(0, len(self.dblines)):
	        if self.dblines[x][0] == 'mhos' or self.dblines[x][0] == 'mhoa':
	            db.write(pack('3I'+str(self.dblines[x][1][0])+'s'+str(self.dblines[x-1][1][5])+'x', *self.dblines[x][1]))
	        elif self.dblines[x][0] == 'mhoe' or self.dblines[x][0] == 'mhoc' or self.dblines[x][0] == 'mhob':
	            db.write(pack(self.formats['mhod'], *self.dblines[x][1]))
	        else:
	            db.write(pack(self.formats[self.dblines[x][0]], *self.dblines[x][1]))
        except error:
	    print x, self.dblines[x][0]
	db.close()
 
    def Save(self, filename=None):
        """ Save all """
	self._UpdateThumbsOffsets()
	self._ThumbsRebuild()
	self._SaveDB(filename)
 
    def Albums(self):
        """ Get current photo albums """
        result = []
        for x in range(0, len(self.dblines)):
            if self.dblines[x][0] == 'mhba':
                albumpics = []
                picsn = self.dblines[x][1][4]
                for y in range(0, picsn):
                    albumpics.append(self.dblines[x+3+y][1][4])
                album = Album(self.dblines[x+2][1][3].decode("utf-8"), albumpics)                    
                result.append(album)
        return result
    
    def Photos(self):
        """ Get current photos details """
        result = []
        for x in range(0, len(self.dblines)):
            if self.dblines[x][0] == 'mhii':
                photo = Photo(self.dblines[x][1][4], self.dblines[x+4][1][3].decode("utf-16-le"))
                result.append(photo)
        return result
    
    def AddAlbum(self, name):
        """ Add a new album """
	namelen = len(name)
	namepad = getPadding(namelen)
	mhob_totlen = 24+12+namelen+namepad
        for x in range(0, len(self.dblines)):
            if self.dblines[x][0] == 'mhla':
                self.dblines[x][1][2] += 1  # Incrementing number of albums
		self.dblines[x-1][1][2] += 148+mhob_totlen  # Incrementing total length of parent MHSD
                mhla = x
            if self.dblines[x][0] == 'mhba':
                last_mhba = x  # Getting the latest album list offset: needed to generate new IDs
	self.dblines[0][1][7] += 1  # Incrementing next ID field in MHFD
        # If iPod Nano, assuming 4 elements at the end of the list: MHSD, MHLF and 2 MHIF
	if self.ipod_model == 'Nano':
	    afteralb = 4
	# Else assuming 6 elements at the end of the list: MHSD, MHLF and 4 MHIF
	else:
	    afteralb = 6
        self.dblines.insert(-afteralb, ['mhba', ['mhba', 148, 148+mhob_totlen, 1, 0, (self.dblines[last_mhba][1][5]+1), 0, 393216, 0, 0, 0, 0, 0, 0, 0, \
                                                           (self.dblines[last_mhba][1][15]+self.dblines[last_mhba][1][4]+1)], mhla])
        self.dblines.insert(-afteralb, ['mhob', ['mhod', 24, mhob_totlen, 1, 0, namepad], len(self.dblines)-6])
        self.dblines.insert(-afteralb, ['mhoa', [namelen, 1, 0, name], len(self.dblines)-6])
 
    def _GetMHNIs(self, mhii):
        """ Get MHNI offsets for the specified MHII and for the current iPod model """
	if self.ipod_model == '5G':
	    return (mhii+6, mhii+10, mhii+14, mhii+18)
	elif self.ipod_model == 'Nano':
	    return (mhii+6, mhii+10)
	else:  # Using iPod 5G MHNI offsets as default
	    return (mhii+6, mhii+10, mhii+14, mhii+18)
 
    def _ThumbsExtract(self):
        """ Extract all the thumbs in a specified hidden directory with a subdirectory per size format """
	thumb_dirs = []
	thumbdims = list(self.thumbdims)
	thumbsizes_len = len(self.thumbsizes)
	for t in range(0, thumbsizes_len):
	    thumb_dirs.append(os.path.join(self.dbdir, '.SingleThumbs', '%sx%s' % (self.thumbsizes[t][0], self.thumbsizes[t][1])))
	    if not os.path.isdir(thumb_dirs[-1]):
	        os.makedirs(thumb_dirs[-1])
	for x in range(0, len(self.dblines)):
	    if self.dblines[x][0] == 'mhii':
	        current_id = self.dblines[x][1][4]
	        for y in self._GetMHNIs(x):
	            d = thumbdims.index(self.dblines[y][1][6])
	            thumbs = open(os.path.join(self.dbdir, 'Thumbs', 'F%s_1.ithmb' % self.thumbfnames[self.dblines[y][1][6]]), 'rb')
		    thumb = open(os.path.join(thumb_dirs[d], str(current_id)), 'w+b') 
	            thumbs.seek(self.dblines[y][1][5])
	            thumb.write(thumbs.read(self.dblines[y][1][6]))
		    thumb.close()
		    thumbs.close()
        self.thumbs_extracted = True
 
    def _ThumbsRebuild(self):
        """ Rebuild the thumbnails file from the single thumbs previously extracted """
	maindir = os.path.join(self.dbdir, '.SingleThumbs')
	for t in range(0, len(self.thumbsizes)):
	    tdir = os.path.join(maindir, '%sx%s' % (self.thumbsizes[t][0], self.thumbsizes[t][1]))
	    tdirlist = os.listdir(tdir)
	    tdirlist.sort()
	    thumbs = open(os.path.join(self.dbdir, 'Thumbs', 'F%s_1.ithmb' % self.thumbfnames[self.thumbdims[t]]), 'w+b')
	    for f in tdirlist:
	        thumbname = os.path.join(tdir, f)
	        thumb = open(thumbname, 'rb')
		thumbs.write(thumb.read())
		thumb.close()
		os.remove(thumbname)
	    thumbs.close()
	    os.rmdir(tdir)
	os.rmdir(maindir)
	self.thumbs_extracted = False
 
    def _UpdateThumbsOffsets(self):
        """ Update the offsets for each thumbnail in MHNI definitions for all photos """
	thumboffsets = [0, 0, 0, 0]
	thumbdims = list(self.thumbdims)
	thumbsizes_len = len(self.thumbsizes)
	for x in range(0, len(self.dblines)):
	    if self.dblines[x][0] == 'mhii':
	        for y in self._GetMHNIs(x):
		    d = thumbdims.index(self.dblines[y][1][6])
		    self.dblines[y][1][5] = thumboffsets[d]
                    thumboffsets[d] += self.dblines[y][1][6]
    
    def RemoveThumbsCache(self):
        """ Remove the cache of the single thumbnails """
        maindir = os.path.join(self.dbdir, '.SingleThumbs')
        for t in self.thumbsizes:
            td = os.path.join(maindir, '%sx%s' % (t[0], t[1]))
            for tf in os.listdir(td):
                os.remove(os.path.join(td, tf))
            os.rmdir(td)
        os.rmdir(maindir)
    
    def _RemovePhotoThumbs(self, photoid):
        """ Remove photo thumbs """
        for t in self.thumbsizes:
	    thumbdir = os.path.join(self.dbdir, '.SingleThumbs', '%sx%s' % (t[0], t[1]))
	    os.remove(os.path.join(thumbdir, str(photoid)))
    
    def _RemovePhotoFromList(self, photoid):
        """ Remove a photo from the photos list (MHLI) """
	for x in range(0, len(self.dblines)):
	    if self.dblines[x][0] == 'mhii':
	        if self.dblines[x][1][4] == photoid:
	            photo = x
	        elif self.dblines[x][1][4] > photoid:
	            #self.dblines[x][1][4] -= 1  # Adjusting IDs for next photos MOVED TO NEW FUNCTION: it CREATED problems when removing!!!
	     	    self.dblines[x][1][5] -= 1
		#	for t in (x+6, x+10, x+14, x+18):
		#	    self.dblines[t][1][5] -= self.dblines[t][1][6]  # Adjusting thumbnails offsets for next images
	    if self.dblines[x][0] == 'mhba':
	        self.dblines[x][1][5] -= 1  # Related decrement of subsequent album IDs
        self.dblines[1][1][2] -= self.dblines[photo][1][2]  # Decrementing total length of parent MHSD
	self.dblines[2][1][2] -= 1  # Decrementing total number of pictures
	self.dblines[0][1][7] -= 1  # Decrementing next ID field in MHFD
	# Removing thumbnails
        self._RemovePhotoThumbs(photoid)
        # Deleting full resolution image
        self._DeleteFullResolution(self.dblines[photo+4][1][3])
	del self.dblines[photo:photo+(4+4*len(self.thumbsizes)+1)]
 
    def _AddPhotoThumbs(self, filename, photoid):
        """ Add photo thumbs """
        thumbsizes_len = len(self.thumbsizes)
        for t in range(0, thumbsizes_len):
	    thumb = open(os.path.join(self.dbdir, '.SingleThumbs', '%sx%s' % (self.thumbsizes[t][0], self.thumbsizes[t][1]), str(photoid)), 'w+b')
	    if t == 0 and thumbsizes_len != 2:  # then is not TV photo neither iPod Nano
	        imgdata = toInterlacedUYVY(filename)
	    else:
	        if t == 0 and self.ipod_model == 'Nano':
		    imgdata = toRGB565(filename, self.thumbsizes[t][0], self.thumbsizes[t][1], False)
		elif t == 1 and (self.ipod_model == 'Photo' or self.ipod_model == 'Color'):
		    imgdata = toRGB565(filename, self.thumbsizes[t][0], self.thumbsizes[t][1], True, True)
		else:
	            imgdata = toRGB565(filename, self.thumbsizes[t][0], self.thumbsizes[t][1])
            thumb.write(imgdata)
            thumb.close()
 
    def _AddPhotoToList(self, filename):
        """ Add a new photo to the photos list (MHLI) """
	name = os.path.basename(filename)  # check maximum filename string length FIXME
	picdate = time.localtime()
	albums = 0
	last_mhii = None
        for x in range(0, len(self.dblines)):
            if self.dblines[x][0] == 'mhii':
                last_mhii = x
	    if self.dblines[x][0] == 'mhba':
	        self.dblines[x][1][5] += 1  # Related increment of subsequent album IDs
		albums += 1
	mhli = 2
	if last_mhii == None:
	    mhpos = mhii = 3
	    photo_id = 100
	else:
	    mhpos = mhii = last_mhii+4+4*len(self.thumbsizes)+1
	    photo_id = self.dblines[last_mhii][1][4]+1
	# Copying full resolution image
	self._CopyFullResolution(filename)
	fullresdb = (':Full Resolution:%s:%s:%s:%s' % (picdate[0], picdate[1], picdate[2], name)).encode('utf-16-le')
	fullresdb_len = len(fullresdb)
	fullresdb_pad = getPadding(fullresdb_len)
	fres = fullresdb_len+fullresdb_pad
	# Incrementing total length of parent MHSD
	self.dblines[1][1][2] += 288+(len(self.thumbdbsizes)*180)+fres
	# Generating thumbnails
        self._AddPhotoThumbs(filename, photo_id)
        # Photo header (MHII)
        self.dblines.insert(mhpos, ['mhii', ['mhii', 152, 288+(len(self.thumbdbsizes)*180)+fres, (1+len(self.thumbdbsizes)), photo_id, photo_id+albums, 0, 0, 0, 0, 
	                                     appleTime(), appleTime(), 0], mhli])
	self.dblines[mhli][1][2] +=1  # Total number of pictures
	self.dblines[0][1][7] += 1  # Incrementing next ID field in MHFD
	# Full resolution
	self.dblines.insert(mhpos+1, ['mhoe', ['mhod', 24, 136+fres, 5, 0, 0], mhpos]); mhpos+=1
	self.dblines.insert(mhpos+1, ['mhni', ['mhni', 76, 112+fres, 1, 1, 0, 0, 0, 0, 0, 0], mhpos]); mhpos+=1
	self.dblines.insert(mhpos+1, ['mhod', ['mhod', 24, 36+fres, 3, 0, fullresdb_pad], mhpos]); mhpos+=1
	self.dblines.insert(mhpos+1, ['mhos', [fullresdb_len, 2, 0, fullresdb], mhpos])
	mhpos+=1
	# Thumbs
	for thumb in range(0, len(self.thumbdbsizes)):
	    if last_mhii == None:
	        offset = 0
	    else:
	        offset = self.dblines[last_mhii+6+4*thumb][1][5]+self.thumbdims[thumb]
	    self.dblines.insert(mhpos+1, ['mhoc', ['mhod', 24, 180, 2, 0, 0], mhii]); mhpos+=1
	    self.dblines.insert(mhpos+1, ['mhni', ['mhni', 76, 156, 1, self.thumbfnames[self.thumbdims[thumb]], offset, self.thumbdims[thumb], 
	                                           0, 0, self.thumbdbsizes[thumb][1], self.thumbdbsizes[thumb][0]], mhpos]); mhpos+=1
	    self.dblines.insert(mhpos+1, ['mhod', ['mhod', 24, 80, 3, 0, 2], mhpos]); mhpos+=1
	    self.dblines.insert(mhpos+1, ['mhos', [42, 2, 0, (':Thumbs:F%s_1.ithmb' % self.thumbfnames[self.thumbdims[thumb]]).encode('utf-16-le')], mhpos]); mhpos+=1
	return photo_id
 
    def _CopyFullResolution(self, filename):
        """ Copy the full resolution photo to destination """
	name = os.path.basename(filename)
	picdate = time.localtime()
	dest_dir = os.path.join(self.dbdir, 'Full Resolution', *map(str, picdate[:3]))
	if not os.path.isdir(dest_dir):
	    os.makedirs(dest_dir)
	shutil.copyfile(filename, os.path.join(dest_dir, name))				    
 
    def _DeleteFullResolution(self, fullres_path):
        """ Delete the full resolution photo from path (Mac, relative) specified """
        filename = fullres_path.decode('utf-16-le')
        filename = os.path.join(self.dbdir, *filename.split(':')[1:])
	try:
	    if os.path.isfile(filename):
                os.remove(filename)
	except OSError:
	    print 'Problems removing %s.' % filename
 
    def AddPhotoToAlbum(self, photo_id, album_id=0):  # Default: adding to library
        """ Add a photo to an album """
	counter = 0
	for x in range(0, len(self.dblines)):
	    if self.dblines[x][0] == 'mhla':
	        self.dblines[x-1][1][2] += 40  # Incrementing total length of parent MHSD
	    if self.dblines[x][0] == 'mhba':
	        if counter == album_id:
		    mhba = x
		    break
	        counter+=1
	self.dblines[mhba][1][2] += 40  # Incrementing total length of parent album
	self.dblines[mhba][1][4] += 1  # Incrementing number of pictures for parent album
	self.dblines.insert(mhba+2+self.dblines[mhba][1][4], ['mhia', ['mhia', 40, 40, 0, photo_id], mhba])
 
    def _RemovePhotoFromAlbums(self, photo_id):
        """ Remove a photo from all the albums which link to it """
	photos_to_remove = []
	for x in range(0, len(self.dblines)):
	    if self.dblines[x][0] == 'mhla':
	        mhla = x
	    if self.dblines[x][0] == 'mhba':
	        current_mhba = x
	    if self.dblines[x][0] == 'mhia':
	        if self.dblines[x][1][4] == photo_id:
	            photos_to_remove.append(x)
		    self.dblines[current_mhba][1][2] -= 40
		    self.dblines[current_mhba][1][4] -= 1
		    self.dblines[mhla-1][1][2] -= 40
	        #elif self.dblines[x][1][4] > photo_id:  # Moved to UpdatePhotoIDs: Created problems when removing!
		#    self.dblines[x][1][4] -= 1
	photos_to_remove.sort(reverse=True)
	for photo in photos_to_remove:
            del self.dblines[photo]
 
    def AddPhoto(self, filename, album_id):
        """ Add a new photo to the file database """
        photo_id = self._AddPhotoToList(filename)
	self.AddPhotoToAlbum(photo_id, 0)  # First adding to the library
	if album_id != 0:
	    self.AddPhotoToAlbum(photo_id, album_id)
 
    def RemovePhoto(self, photo_id):
        """ Remove a photo from the file database """
	self._RemovePhotoFromAlbums(photo_id)
	self._RemovePhotoFromList(photo_id)
 
    def _UpdatePhotoIDs(self):
        """ Update IDs of the photos avoiding more than +1 differences between numbers """
        mhii = 100
        IDs = {}
        for x in range(0, len(self.dblines)):
            if self.dblines[x][0] == 'mhii':
                IDs[self.dblines[x][1][4]] = mhii
                self.dblines[x][1][4] = mhii
                mhii += 1
            if self.dblines[x][0] == 'mhia':
                self.dblines[x][1][4] = IDs[self.dblines[x][1][4]]
 
    def RemoveAlbum(self, album_id):
        """ Remove an existing album """
	if album_id == 0:
	    print "Could not remove photo library!"
	    sys.exit()
	try:
	    mhba = 0
	    for x in range(0, len(self.dblines)):
	        if self.dblines[x][0] == 'mhla':
	            mhla = x
                if self.dblines[x][0] == 'mhba':
	            if mhba == album_id:
	                self.dblines[mhla][1][2] -= 1  # Decrementing number of albums in album list (MHLA)
	                self.dblines[mhla-1][1][2] -= self.dblines[x][1][2]  # Decrementing album total length from parent MHSD
		        del self.dblines[x:x+self.dblines[x][1][4]+3]  # Actually deleting slice of the album, related MHOD and children MHIAs
		        break
		    mhba += 1
        except IndexError:
	    print "Index error in RemoveAlbum: seems not fatal, but should be fixed"
    
    def RemoveAlbumAndPhotos(self, album_id):
        """ Remove an existing album actually deleting all its photos, too """
        albums = self.Albums()
        album_pics = albums[album_id].pics
        for pic in album_pics:
            self.RemovePhoto(pic)
        mhba = 0
        for x in range(0, len(self.dblines)):
            if self.dblines[x][0] == 'mhla':
                mhla = x
            if self.dblines[x][0] == 'mhba':
                if mhba == album_id:
	            self.dblines[mhla][1][2] -= 1  # Decrementing number of albums in album list (MHLA)
	            self.dblines[mhla-1][1][2] -= self.dblines[x][1][2]  # Decrementing album total length from parent MHSD
                    del self.dblines[x:x+3]  # Actually deleting slice of the album, related MHOD and string subheader
                    break
                mhba += 1 
 
    def RenameAlbum(self, album_id, new_album_name):  # Maybe we should check the length of the provided name...
        """ Rename a photo album """
	new_album_name = new_album_name.encode('utf-8')
	new_album_name_len = len(new_album_name)
	new_album_name_pad = getPadding(new_album_name_len)
	mhba = 0
	for x in range(0, len(self.dblines)):
	    if self.dblines[x][0] == 'mhla':
	        mhla = x
	    if self.dblines[x][0] == 'mhba':
	        if mhba == album_id:
		    old_length = self.dblines[x+2][1][0] + self.dblines[x+1][1][5]  # Old string name + padding length
		    new_length = new_album_name_len + new_album_name_pad
		    if old_length != new_length:
		        diff_length = new_length - old_length  # Needed to update parent containers' total lengths
			self.dblines[mhla-1][1][2] += diff_length  # Updating parent MHSD
			self.dblines[x][1][2] += diff_length  # Updating parent album
			self.dblines[x+1][1][2] += diff_length  # Updating parent string MHOD (MHOB)
		    self.dblines[x+1][1][5] = new_album_name_pad
		    self.dblines[x+2][1][0] = new_album_name_len
		    self.dblines[x+2][1][3] = new_album_name
		mhba += 1
 
    def FindLostPhotos(self, in_library=False):
        """ Find photos currently not associated to any album """
        lostphotos = []
        mhba = 0
        for x in range(0, len(self.dblines)):
            if self.dblines[x][0] == 'mhii':
                lostphotos.append(self.dblines[x][1][4])
            if self.dblines[x][0] == 'mhba':
                if in_library == True:
                    if mhba == 0:
                        process = True
                    else:
                        process = False
                else:
                    if mhba > 0:
                        process = True
                    else:
                        process = False
                mhba += 1
            if self.dblines[x][0] == 'mhia' and process == True:
                lostphotos.remove(self.dblines[x][1][4])
        return lostphotos
 
    def _PrepareToSave(self):
        """ Adjust the contents list to be written to the database file, updating total lengths, number of children, etc. """
	# Adjusting total length of MHFD
	self.dblines[0][1][2] = 132
	for x in range(0, len(self.dblines)):
	    if self.dblines[x][0] == 'mhsd':
	        self.dblines[0][1][2] += self.dblines[x][1][2]
	self._UpdatePhotoIDs()
 
 
# STRUCTURES
 
class Album:
    """ Album structure """
    def __init__(self, name, pics):
        """ Fill the album structure """
        self.name = name
        self.pics = pics
 
class Photo:
    """ Photo structure """
    def __init__(self, id, fullres):
        " Fill the photo structure """
        self.id = id
        self.fullres = fullres
 
 
if __name__ == '__main__':
    try:
        DB = MH('./Photos')
        for album in DB.Albums():
            print album.name, '-', len(album.pics), 'pictures:', album.pics
        for photo in DB.Photos():
            print photo.id, photo.fullres
    except KeyboardInterrupt:
        print "Interruption requested by user"