packages feed

bkr-0.1.0: src/System/Bkr/BkrLocalFile.hs

{-# LANGUAGE ScopedTypeVariables #-}

module System.Bkr.BkrLocalFile ( getBkrMetaForLocalPaths
                        --, getBkrObjects
                        --, getAllFolders
                        --, getAllFiles
                        ) where

import System.Bkr.Hasher
import System.Bkr.BkrFundare
import System.Bkr.BkrLocalMeta
import System.Bkr.BkrConfig (getFilesToIgnore, getFileExtensionsToIgnore, getFoldersToIgnore, getFileUpdateCheckType, FileUpdateCheckType(..))

import Control.Monad (filterM, liftM)
import System.Directory (doesDirectoryExist, getDirectoryContents, doesFileExist, getModificationTime)
import System.FilePath ((</>), normalise, takeExtensions, takeDirectory)
import Prelude hiding (catch)
import Control.Exception

--import Data.Digest.Pure.MD5 (md5, MD5Digest)
--import qualified Data.ByteString.Lazy as L

{-| Filter [FilePath] on file extension. The first argument is a list with file extensions to filter on (for example [\".txt\", \".doc\"]) and the second a list of the file paths to filter. -}
filterExt :: [FilePath] -> [FilePath] -> [FilePath]
filterExt ignoreList = filter (\x -> takeExtensions x `notElem` ignoreList)

{-| Filter [FilePath] on folders. The first argument is a list with folders to filter on (for example [\"/Users/username/donotbackup\", \"/Users/username/donotbackupeither\"] and the second a list of the file paths to filter. -}
filterFolder :: [FilePath] -> [FilePath] -> [FilePath]
filterFolder ignoreList = filter (\x -> takeDirectory x `notElem` ignoreList)

{-| Filter [FilePath] on files. The first argument is a list with files to filter on (for example [\"donotbackup\", \"donotbackupeither\"] and the second a list of the file paths to filter. -}
filterFile :: [FilePath] -> [FilePath] -> [FilePath]
--filterFile ignoreList = filter (\x -> x `notElem` ignoreList)
filterFile ignoreList = filter (`notElem` ignoreList)

{-| Handle IOException. Check if the passed FilePath is a file and return [FilePath] if it is and [] if not. Used, for instance, if you want to make sure that you don't miss a file if FilePath passed to getDirectoryContents is a file. -}
handleIOReturnIfFile :: IOException -> FilePath -> IO [FilePath]
handleIOReturnIfFile _ path = do
     pathIsFile <- doesFileExist path
     if pathIsFile
        then return [path]
        else return []

{-| Get files to ignore from bkr.conf and add some default files that always should be ignored. -}
getFilesToFilter :: IO [FilePath]
getFilesToFilter = --do
     --filesToFilter <- getFilesToIgnore
     --return $ [".", "..", ".bkrmeta"] ++ filesToIgnore
     getFilesToIgnore >>= return . (++) [".", "..", ".bkrmeta"]

{-| Get all files for a given folder filtered on file extension and files to ignore. -}
getFilesInFolder :: FilePath -> IO [FilePath]
getFilesInFolder path = do
     names <- getDirectoryContents path `catch` \ (ex :: IOException) -> handleIOReturnIfFile ex path
     filesToIgnore <- getFilesToFilter
     fileExtToIgnore <- getFileExtensionsToIgnore
     -- Filter files on extension and files to ignore
     let properNames = filterExt fileExtToIgnore $ filterFile filesToIgnore names
     -- Map files with topPath to get full path and filter on files
     filterM doesFileExist (map (path </>) properNames)

{-| Get all folders for a given path filtered on folders to ignore. -}
getAllFolders :: FilePath -> IO [FilePath]
getAllFolders topPath = do
     names <- getDirectoryContents topPath `catch` \ (_ :: IOException) -> return []
     -- Map files with topPath to get full path and filter on folders to ignore
     foldersToIgnore <- getFoldersToIgnore
     let paths = filterFolder foldersToIgnore $ map ((</>) topPath) (filterFile [".", ".."] names)
     folders <- filterM doesDirectoryExist paths
     allFolders <- mapM getAllFolders folders

     return $ map normalise (concat allFolders ++ folders)

{-| Get a BkrMeta for only path and modification time that can be used with BkrMeta_ for Eq on path and modification time checksum. -}
getBkrMeta_ :: FilePath -> IO BkrMeta
getBkrMeta_ path = do
     modTime <- getModificationTime path
     return $ BkrMeta path "" (show $ getHashForString path) "" (show $ getHashForString $ show modTime)

{-| 
Get file update check type:
    checksum -> do not check .bkrmeta files
    date and smart -> pass type to getLocalMeta 
-}
getCachedBkrMetaList :: FilePath -> IO [BkrMeta]
getCachedBkrMetaList dotbkrmetaFile = do
     updateFileCheck <- getFileUpdateCheckType
     if updateFileCheck == FUCChecksum
        then return []
        else do 
          -- Check that the .bkrmeta file exist and get all cached BkrMeta objects
          bkrMetaFile <- filterM doesFileExist [dotbkrmetaFile]
          let getLocalMeta' = getLocalMeta updateFileCheck
          --cachedBkrMetaList <- mapM getLocalMeta' bkrMetaFile
          --return $ concat cachedBkrMetaList
          mapM getLocalMeta' bkrMetaFile >>= return . concat

{-| 
For given FilePath:
    - Get all BkrMeta objects from .bkrmeta (if it exists)
    - Check if the .bkrmeta objects are valid or should be updated according to file update check type (see getCachedBkrMetaList)
    - Update .bkrmeta by inserting new and updated objects and deleting objects for files that have been deleted
    - Return all BkrMeta objects for the given FilePath
-}
folderCompareAndGetBkrMeta :: FilePath -> IO [BkrMeta]
folderCompareAndGetBkrMeta path = do
     let dotbkrmetaFile = path ++ "/.bkrmeta"

     -- Get all files in folder and getBkrMeta_ them (getBkrMeta_ does not get all BkrMeta items since folderBkrMetaList is only going to be used for filtering which files are not cached in .bkrmeta)
     --filesInFolder <- getFilesInFolder path
     --folderBkrMetaList <- mapM getBkrMeta_ filesInFolder
     folderBkrMetaList <- getFilesInFolder path >>= mapM getBkrMeta_

     -- Get all cached BkrMeta objects and mind file update check type setting; checksum -> do not check .bkrmeta files (get checksum for all files every time), date (check by file change date) and smart (get the checksum randomly but always at least every tenth run).
     cachedBkrMetaList' <- getCachedBkrMetaList dotbkrmetaFile

     -- Get all cached files that has been deleted from the disk and delete them from the .bkrmeta file
     let deletedCachedFiles = filter (`notElem` folderBkrMetaList) cachedBkrMetaList'
     deleteBkrMeta dotbkrmetaFile deletedCachedFiles
     -- Get the cached files that has not been deleted from disk
     let cachedBkrMetaList = filter (`notElem` deletedCachedFiles) cachedBkrMetaList'
     
     -- Get all files that are in folderBkrMetaList but not in cachedBkrMetaList
     let notCachedBkrMetaList = filter (`notElem` cachedBkrMetaList) folderBkrMetaList
     
     -- Get "real" BkrMeta objects and update .bkrmeta file with notCachedBkrMetaList
     let notCachedPaths = map fullPath notCachedBkrMetaList
     notCachedBkrMeta <- mapM getBkrMeta notCachedPaths
     insertBkrMeta dotbkrmetaFile notCachedBkrMeta
     
     -- Return cached and not cached BkrMeta objects
     return $ notCachedBkrMeta ++ cachedBkrMetaList

{-| Get all BkrMeta objects for a list of folders using .bkrmeta cached objects according to the bkr.conf fileupdatecheck setting. -}
getBkrMetaForLocalPaths :: [FilePath] -> IO [BkrMeta]
getBkrMetaForLocalPaths paths = do
     allPaths <- mapM getAllFolders paths
     --bkrMeta <- mapM folderCompareAndGetBkrMeta (concat allPaths ++ paths)
     --return $ concat bkrMeta
     --mapM folderCompareAndGetBkrMeta (concat allPaths ++ paths) >>= return . concat
     liftM concat (mapM folderCompareAndGetBkrMeta ((++) paths (concat allPaths)))

{-
getBkrMeta'' :: [FilePath] -> IO [BkrMeta]
getBkrMeta'' paths = do
     -- Get all folders, combine with .bkrmeta file for full file path to the .bkrmeta files, filter to to check which .bkrmeta files exist, get BkrMeta objects and finally get BkrMeta_ objects
     allFolders <- mapM getAllFolders paths
     allBkrLocalMetaFiles <- filterM doesFileExist (map combine_ (concat allFolders))
     localMetaDB <- mapM getLocalMeta allBkrLocalMetaFiles
     let localMeta_ = map BkrMeta_ (concat localMetaDB)
     
     -- Get all files
     allFiles <- mapM getAllFiles paths
     localMetaFiles <- mapM getBkrMeta (concat allFiles)
     let localMetaFiles_ = map BkrMeta_ localMetaFiles
     let notCached = filter (`notElem` localMeta_) localMetaFiles_

     let allFiles = map fullPath (map bkrMeta notCached)

     notCachedMeta <- mapM getBkrMeta allFiles --(fullPath (BkrMeta notCached))


     --let allMeta = (concat localMetaDB) ++ notCachedMeta

     return $ (concat localMetaDB) ++ notCachedMeta
-}
{-
getAllFiles :: FilePath -> IO [FilePath]
getAllFiles topPath = do
     names <- getDirectoryContents topPath `catch` \ (ex :: IOException) -> handleIO ex topPath
     --filesToIgnore <- getFilesToIgnore
     filesToIgnore <- getFilesToFilter
     fileExtToIgnore <- getFileExtensionsToIgnore
     foldersToIgnore <- getFoldersToIgnore
     -- Filter files on extension and files to ignore
     --let properNames = (filterExt fileExtToIgnore) $ filterFile ([".", "..", ".bkrmeta"] ++ filesToIgnore) names
     let properNames = filterExt fileExtToIgnore $ filterFile filesToIgnore names
     -- Map files with topPath to get full path and filter on folders to ignore
     let allPaths = filterFolder foldersToIgnore $ map (topPath </>) properNames
     paths <- forM allPaths $ \path -> do
           isDirectory <- doesDirectoryExist path
           if isDirectory
              then getAllFiles path
              else return [normalise path]
     return $ concat paths
-}
{-
getAllFilesOld :: FilePath -> IO [FilePath]
getAllFilesOld topPath = do
     names <- getDirectoryContents topPath `catch` \ (ex :: IOException) -> handleIO ex topPath
     --filesToIgnore <- getFilesToIgnore
     filesToIgnore <- getFilesToFilter
     --let properNames = filter (`notElem` ([".", ".."] ++ filesToIgnore)) names
     let properNames = filter (`notElem` (filesToIgnore)) names
     print $ show properNames
     paths <- forM properNames $ \name -> do
           let path = topPath </> name
           isDirectory <- doesDirectoryExist path
           if isDirectory
              then getAllFiles path
              else return [normalise path]
     return $ concat paths
-}
{-
getBkrMeta :: FilePath -> IO BkrMeta
getBkrMeta path = do
     fileHash <- getFileHash path
     return $ BkrMeta path (show fileHash) (show $ getHashForString path)
-}
{-
getBkrObjects :: FilePath -> IO [BkrMeta]
getBkrObjects path = --do
     --allFiles <- getAllFiles path
     --mapM getBkrMeta allFiles
     getAllFiles path >>= mapM getBkrMeta
-}
{-
getLocalBkrObjects :: FilePath -> IO [BkrMeta]
getLocalBkrObjects path = do
     folders <- getAllFolders path
     return $ mapM getBkrMetaFromDB folders
-}
{-
getBkrMeta' :: FilePath -> IO [BkrMeta]
getBkrMeta' = do
     folders <- getAllFolders path
     return $ concat $ map getBkrMeta'' folders
-}
{-
combine_ :: FilePath -> FilePath
combine_ path = (++) path "/.bkrmeta"
-}