packages feed

hscdio-0.1.0.0: src/Foreign/Libcdio/CdTypes.hs

{-# LANGUAGE ForeignFunctionInterface #-}

{-|
Description:    Objects used to describe the CD and filesystem format.

Copyright:      (c) 2019-2021 Sam May
License:        GPL-3.0-or-later
Maintainer:     ag@eitilt.life

Stability:      stable
Portability:    non-portable (requires libcdio)

Like any drive, discs may store their data according to many different
filesystem structures.  These are abstracted to a degree in libcdio, but as
each track may store its data differently, and at the very least 'Audio' is
handled differently than any of the file-oriented layouts, it is still helpful
to know the underlying structure.


= @cd_types.h@

== Defines
* CDIO_FSTYPE                       (removed; cdio_fs_anal_t represented differently in Haskell)
* CDIO_FS_UNKNOWN                   (removed; handled via 'Nothing')

== Types
* @cdio_fs_t@                       -> 'Foreign.Libcdio.CdTypes.Filesystem'

    - @CDIO_FS_INTERACTIVE@         -> 'Foreign.Libcdio.CdTypes.CdI'
    - @CDIO_FS_ISO_HFS@             -> 'Foreign.Libcdio.CdTypes.Hfs' with a 'Just' 'Foreign.Libcdio.CdTypes.IsoAnalysis'
    - @CDIO_FS_ISO_9660_INTERACTIVE@ -> 'Foreign.Libcdio.CdTypes.CdI' with a 'Just' 'Foreign.Libcdio.CdTypes.IsoAnalysis'
    - @CDIO_FS_ISO_UDF@             -> 'Foreign.Libcdio.CdTypes.Udf' with a 'Just' 'Foreign.Libcdio.CdTypes.IsoAnalysis'
    - @CDIO_FS_ISO_XISO@            -> 'Foreign.Libcdio.CdTypes.XboxIso'
    - @CDIO_FS_ISO_UDFX@            -> 'Foreign.Libcdio.CdTypes.XboxUdf'

* @cdio_fs_cap_t@                   -> 'Foreign.Libcdio.CdTypes.FilesystemClass'

    Note that some values are not included in the Haskell type, as the indexing
    implementation is stricter than the equivalent bit operations in C.

    - @CDIO_FS_MASK@                (removed; cdio_fs_anal_t represented differently in Haskell)
    - @CDIO_FS_ANAL_VCD_ANY@        -> 'Foreign.Libcdio.CdTypes.fsAnyVcd'
    - @CDIO_FS_MATCH_ALL@           -> 'Foreign.Libcdio.CdTypes.fsAny'
    - @CDIO_FS_ANAL_CDTV@           -> 'Foreign.Libcdio.CdTypes.CommodoreCdtv'
    - @CDIO_FS_ANAL_SVCD@           -> 'Foreign.Libcdio.CdTypes.SuperVideoCd'
    - @CDIO_FS_ANAL_CVD@            -> 'Foreign.Libcdio.CdTypes.ChoijiVideoCd'
    - @CDIO_FS_ANAL_XISO@           -> 'Foreign.Libcdio.CdTypes.XboxIsoClass'

* @cdio_iso_analysis_t@             -> 'Foreign.Libcdio.CdTypes.IsoAnalysis'

    - @joliet_level@                -> 'Foreign.Libcdio.CdTypes.jolietLevel'
    - @iso_label@                   -> 'Foreign.Libcdio.CdTypes.label'
    - @isofs_size@                  -> 'Foreign.Libcdio.CdTypes.fsSize'
    - @UDFVerMajor@                 -> 'fst' 'Foreign.Libcdio.CdTypes.udfVersion'
    - @UDFVerMinor@                 -> 'snd' 'Foreign.Libcdio.CdTypes.udfVersion'

== Symbols
* @cdio_guess_cd_type@              -> 'Foreign.Libcdio.CdTypes.trackType'


= "Sound.Libcdio.Read.Filesystem"

* 'trackType'                       -> 'Sound.Libcdio.Read.Filesystem.filesystemType'
-}
module Foreign.Libcdio.CdTypes
    ( -- * Types
      Filesystem ( .. )
    , FilesystemClass ( .. )
    , FilesystemClasses
    , fsAny, fsAnyVcd
    , IsoAnalysis ( .. )
      -- * Access
    , trackType
    ) where


import qualified Data.Maybe as Y
import qualified Data.Word as W

import qualified Data.Array.BitArray as A

import qualified Foreign.C.String as C
import qualified Foreign.C.Types as C
import qualified Foreign.Ptr as C

import qualified Foreign.Marshal.Alloc as M
import qualified Foreign.Storable as S

import Foreign.Libcdio.Marshal
import Foreign.Libcdio.Track
import Foreign.Libcdio.Types.Enums
import Foreign.Libcdio.Types.Offsets
import Foreign.Libcdio.Types.Internal


-- | Types of filesystem which may be read from a disc.
data Filesystem
    = Audio
    | HighSierra
    | Iso9660
    | CdI
    | Cd3do
    | Ext2
    | Hfs
    | Ufs
    | Udf
    | XboxIso
    | XboxUdf
  deriving ( Eq, Show, Read, Ord, Enum, Bounded )


-- | A collection of disc/filesystem descriptions.
type FilesystemClasses = A.BitArray FilesystemClass

-- | Any sort of Video CD.
fsAnyVcd :: FilesystemClasses
fsAnyVcd = genBitArray
    [ VideoCd
    , SuperVideoCd
    , ChoijiVideoCd
    ]

-- | The set of every 'FilesystemClass'.
fsAny :: FilesystemClasses
fsAny = genBitArray [minBound .. maxBound]


-- | A collection of data describing a disc using the ISO 9660 standard.
data IsoAnalysis = IsoAnalysis
    { jolietLevel :: Maybe Word
    , label       :: String
    , fsSize      :: Word
    , udfVersion  :: Maybe (W.Word8, W.Word8)
    }
  deriving ( Eq, Show, Read )
instance S.Storable IsoAnalysis where
    sizeOf _    = iaSizeOf
    alignment _ = iaAlign
    peek c = do
        j <- S.peekByteOff c iaJolietLevel :: IO C.CUInt
        l <- C.peekCString $ C.plusPtr c iaLabel
        s <- S.peekByteOff c iaIsoSize :: IO C.CUInt
        a <- S.peekByteOff c iaUDFMajor
        i <- S.peekByteOff c iaUDFMinor
        return $ IsoAnalysis
            { jolietLevel = if j == 0 then Nothing else Just $ fromIntegral j
            , label = l
            , fsSize = fromIntegral s
            , udfVersion = if a == 0 then Nothing else Just (a, i)
            }
    poke c hs = do
        let (a, i) = Y.fromMaybe (0, 0) $ udfVersion hs
        S.pokeByteOff c iaJolietLevel . maybe (0 :: C.CUInt) fromIntegral $ jolietLevel hs
        pokeCString (label hs) 33 $ C.plusPtr c iaLabel
        S.pokeByteOff c iaIsoSize (fromIntegral $ fsSize hs :: C.CUInt)
        S.pokeByteOff c iaUDFMajor a
        S.pokeByteOff c iaUDFMinor i


-- | Determine what type of file system is stored in the given track of a disc.
trackType :: Cdio -> Track -> IO (Maybe Filesystem, FilesystemClasses, Maybe IsoAnalysis)
trackType c t = do
    l' <- trackLsn c t
    (fs, iso') <- case l' of
        Just l -> withCdio' defaultCdType $ \c' -> M.alloca $ \i' -> do
            e' <- cdType_ c' l (withTrack t) i'
            iso <- S.peek i'
            return (modEnumFlags e', iso)
        Nothing -> return defaultCdType
    let fc = snd fs
        iso = Just iso' { udfVersion = Nothing }
        isoUdf = Just iso'
    return $ case fst fs of
        Nothing -> (Nothing, fc, isoUdf)
        Just Fs3do -> (Just Cd3do, fc, iso)
        Just FsAudio -> (Just Audio, fc, Nothing)
        Just FsExt2 -> (Just Ext2, fc, iso)
        Just FsHfs -> (Just Hfs, fc, Nothing)
        Just FsIsoHfs -> (Just Hfs, fc, iso)
        Just FsHighSierra -> (Just HighSierra, fc, iso)
        Just FsInteractive -> (Just CdI, fc, Nothing)
        Just FsIso9660Interactive -> (Just CdI, fc, iso)
        Just FsIso9660 -> (Just Iso9660, fc, iso)
        Just FsUdf -> (Just Udf, fc, Nothing)
        Just FsIsoUdf -> (Just Udf, fc, isoUdf)
        Just FsUdfx -> (Just Udf, fc, isoUdf)
        Just FsUfs -> (Just Ufs, fc, iso)
        Just FsXiso -> (Just XboxIso, fc, iso)
  where defaultCdType = ((Nothing, genBitArray []), IsoAnalysis Nothing "" 0 Nothing)
        withCdio' b = fmap (Y.fromMaybe b) . withCdio c

foreign import ccall safe "cdio/compat/cd_types.h cdio_guess_cd_type"
  cdType_ :: C.Ptr Cdio -> Lsn -> CTrack -> C.Ptr IsoAnalysis -> IO C.CInt