# -----------------------------------------------------------------------
# discinfo.py - basic class for any discs containing collections of
# media.
# -----------------------------------------------------------------------
# $Id: discinfo.py 378 2004-09-14 20:12:25Z dischi $
#
# $Log$
# Revision 1.23  2004/09/14 20:12:25  dischi
# fix future warning
#
# Revision 1.22  2004/08/28 17:27:16  dischi
# handle empty discs
#
# Revision 1.21  2004/06/25 13:20:35  dischi
# FreeBSD patches
#
# Revision 1.20  2004/02/08 17:44:05  dischi
# close fd
#
# Revision 1.19  2004/01/24 18:40:44  dischi
# add md5 as possible way to generate the id
#
# Revision 1.18  2003/11/07 09:43:40  dischi
# make interface compatible to old one
#
# Revision 1.17  2003/11/05 20:58:26  dischi
# detect mixed audio cds
#
# Revision 1.16  2003/10/18 11:13:03  dischi
# patch from Cyril Lacoux to detect blank discs
#
# Revision 1.15  2003/09/23 13:51:27  outlyer
# More *BSD fixes from Lars; repairs a problem in his older patch.
#
# Revision 1.14  2003/09/14 13:32:12  dischi
# set self.id for audio discs in discinfo.py to make it possible to use the cache for cddb
#
# Revision 1.13  2003/09/14 01:41:37  outlyer
# FreeBSD support
#
# Revision 1.12  2003/09/10 18:50:31  dischi
# error handling
#
# Revision 1.11  2003/08/26 21:21:18  outlyer
# Fix two more Python 2.3 warnings.
#
# Revision 1.10  2003/08/26 18:01:26  outlyer
# Patch from Lars Eggert for FreeBSD support
#
# Revision 1.9  2003/08/23 17:54:14  dischi
# move id translation of bad chars directly after parsing
#
# Revision 1.8  2003/07/04 15:32:52  outlyer
# Fix the label so we don't try to cache into a directory instead of a file.
#
# Revision 1.7  2003/07/02 22:04:26  dischi
# just to be save
#
# Revision 1.6  2003/07/02 22:03:13  dischi
# cache the disc id, it cannot change in 1 sec
#
# Revision 1.5  2003/06/23 19:26:16  dischi
# Fixed bug in the cdrommodule that the file was not closed after usage.
# The result was a drive you can't eject while the program (e.g. Freevo)
# is running. Added cvs log for DiscID and cdrommodule to keep track of
# all changes we did for mmpython.
#
# Revision 1.4  2003/06/23 09:22:54  the_krow
# Typo and Indentation fixes.
#
# Revision 1.3  2003/06/10 22:11:36  dischi
# some fixes
#
# Revision 1.2  2003/06/10 11:50:52  dischi
# Moved all ioctl calls for discs to discinfo.cdrom_disc_status. This function
# uses try catch around ioctl so it will return 0 (== no disc) for systems
# without ioctl (e.g. Windows)
#
# Revision 1.1  2003/06/10 10:56:54  the_krow
# - Build try-except blocks around disc imports to make it run on platforms
#   not compiling / running the C extensions.
# - moved DiscInfo class to disc module
# - changed video.VcdInfo to be derived from CollectionInfo instead of
#   DiskInfo so it can be used without the cdrom extensions which are
#   hopefully not needed for bin-files.
#
#
# -----------------------------------------------------------------------
# Copyright (C) 2003 Thomas Schueppel, Dirk Meyer
#
# 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 MER-
# CHANTABILITY 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.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# ----------------------------------------------------------------------- */
#endif

from mmpython import mediainfo
import os
import re
import time
import array
import md5
from struct import *

CREATE_MD5_ID = 0

try:
    from fcntl import ioctl
    import DiscID
except:
    print 'WARNING: failed to import ioctl, discinfo won\'t work'


def cdrom_disc_status(device, handle_mix = 0):
    """
    check the current disc in device
    return: no disc (0), audio cd (1), data cd (2), blank cd (3)
    """
    CDROM_DRIVE_STATUS=0x5326
    CDSL_CURRENT=( (int ) ( ~ 0 >> 1 ) )
    CDROM_DISC_STATUS=0x5327
    CDS_AUDIO=100
    CDS_MIXED=105
    CDS_DISC_OK=4
    
    # FreeBSD ioctls - there is no CDROM.py
    # XXX 0xc0086305 below creates a warning, but 0xc0086305L
    # doesn't work. Suppress that warning for Linux users,
    # until a better solution can be found.
    if os.uname()[0] == 'FreeBSD':
        CDIOREADTOCENTRYS = 0xc0086305L
        CD_MSF_FORMAT = 2
        
    try:
        fd = os.open(device, os.O_RDONLY | os.O_NONBLOCK)
        if os.uname()[0] == 'FreeBSD':
            try:
                cd_toc_entry = array.array('c', '\000'*4096)
                (address, length) = cd_toc_entry.buffer_info()
                buf = pack('BBHP', CD_MSF_FORMAT, 0, length, address)
                s = ioctl(fd, CDIOREADTOCENTRYS, buf)
                s = CDS_DISC_OK
            except:
                s = CDS_NO_DISC
        else:
            s = ioctl(fd, CDROM_DRIVE_STATUS, CDSL_CURRENT)
    except:
        print 'ERROR: no permission to read %s' % device
        print 'Media detection not possible, set drive to \'empty\''
        
        # maybe we need to close the fd if ioctl fails, maybe
        # open fails and there is no fd, maye we aren't running
        # linux and don't have ioctl
        try:
            os.close(fd)
        except:
            pass
        return 0

    if not s == CDS_DISC_OK:
        # no disc, error, whatever
        return 0
    
    if os.uname()[0] == 'FreeBSD':
        s = 0
        # We already have the TOC from above
        for i in range(0, 4096, 8):
            control = unpack('B', cd_toc_entry[i+1])[0] & 4
            track = unpack('B', cd_toc_entry[i+2])[0]
            if track == 0:
                break
            if control == 0 and s != CDS_MIXED:
                s = CDS_AUDIO
            elif control != 0:
                if s == CDS_AUDIO:
                    s = CDS_MIXED
                else:
                    s = 100 + control # ugly, but encodes Linux ioctl returns
            elif control == 5:
                s = CDS_MIXED

    else:
        s = ioctl(fd, CDROM_DISC_STATUS)
    os.close(fd)
    if s == CDS_MIXED and handle_mix:
        return 4
    if s == CDS_AUDIO or s == CDS_MIXED:
        return 1
    
    try:
        fd = open(device, 'rb')
        # try to read from the disc to get information if the disc
        # is a rw medium not written yet
        
        fd.seek(32768) # 2048 multiple boundary for FreeBSD
        # FreeBSD doesn't return IOError unless we try and read:
        fd.read(1)
    except IOError:
        # not readable
    	fd.close()
	return 3
    else:
        # disc ok
    	fd.close()
    	return 2
    

id_cache = {}

def cdrom_disc_id(device, handle_mix=0):
    """
    return the disc id of the device or None if no disc is there
    """
    global id_cache
    try:
        if id_cache[device][0] + 0.9 > time.time():
            return id_cache[device][1:]
    except:
        pass

    disc_type = cdrom_disc_status(device, handle_mix=handle_mix)
    if disc_type == 0 or disc_type == 3:
        return 0, None
        
    elif disc_type == 1 or disc_type == 4:
        disc_id = DiscID.disc_id(device)
        id = '%08lx_%d' % (disc_id[0], disc_id[1])
    else:
        f = open(device,'rb')

        if os.uname()[0] == 'FreeBSD':
            # FreeBSD can only seek to 2048 multiple boundaries.
            # Below works on Linux and FreeBSD:
            f.seek(32768)
            id = f.read(829)
            label = id[40:72]
            id = id[813:829]
        else:
            f.seek(0x0000832d)
            id = f.read(16)
            f.seek(32808, 0)
            label = f.read(32)

        if CREATE_MD5_ID:
            id_md5 = md5.new()
            id_md5.update(f.read(51200))
            id = id_md5.hexdigest()

        f.close()
            
        m = re.match("^(.*[^ ]) *$", label)
        if m:
            id = '%s%s' % (id, m.group(1))
        id = re.compile('[^a-zA-Z0-9()_-]').sub('_', id)
            
        
    id_cache[device] = time.time(), disc_type, id
    id = id.replace('/','_')
    return disc_type, id


class DiscInfo(mediainfo.CollectionInfo):
    def isDisc(self, device):
        (type, self.id) = cdrom_disc_id(device, handle_mix=1)
        if type != 2:
            if type == 4:
                self.keys.append('mixed')
                self.mixed = 1
                type = 1 
            return type
        
        if CREATE_MD5_ID:
            if len(self.id) == 32:
                self.label = ''
            else:
                self.label = self.id[32:]
        else:
            if len(self.id) == 16:
                self.label = ''
            else:
                self.label = self.id[16:]

        self.keys.append('label')
        return type
