squeeze-1.0.4.16: src-lib/Squeeze/Data/FileCombination.hs
{-
Copyright (C) 2010-2016 Dr. Alistair Ward
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 3 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, see <http://www.gnu.org/licenses/>.
-}
{- |
[@AUTHOR@] Dr. Alistair Ward
[@DESCRIPTION@] A data-type which references a set of files by their paths, and qualifies them with their aggregate size.
-}
module Squeeze.Data.FileCombination(
-- * Types
-- ** Data-types
FileCombination(
-- MkFileCombination,
getAggregateFileSize,
getFilePathList
),
-- * Constants
nullFileCombination,
-- * Functions
comparingAggregateFileSize,
prepend,
risingFilter,
-- risingMerge,
risingMergeByAggregateFileSize,
-- ** Constructors
mkFileCombination,
singleton,
-- ** Predicates
hasSizeBy
) where
import qualified Data.List
import qualified Data.Ord
import qualified Squeeze.Data.File as Data.File
import qualified ToolShed.Data.Foldable
import qualified ToolShed.SelfValidate
-- | Declare a list of files qualified by its aggregate size.
data FileCombination = MkFileCombination {
getAggregateFileSize :: !Data.File.FileSize, -- ^ The aggregate size of the files referenced by 'getFilePathList'.
getFilePathList :: Data.File.FilePathList -- ^ A list of paths, defining a set of files.
} deriving Eq
instance Show FileCombination where
showsPrec _ fileCombination = shows (getAggregateFileSize fileCombination) . showChar '\t' . shows (Data.List.sort $ getFilePathList fileCombination)
instance ToolShed.SelfValidate.SelfValidator FileCombination where
getErrors fileCombination = ToolShed.SelfValidate.extractErrors [
(
getAggregateFileSize fileCombination < 0,
"aggregate size must be positive; " ++ show fileCombination
), (
getAggregateFileSize fileCombination /= 0 && null (getFilePathList fileCombination),
"when zero files are specified, the aggregate size must also be zero; " ++ show fileCombination
), let
duplicateFilePaths = map head . filter ((> 1) . length) . ToolShed.Data.Foldable.gather $ getFilePathList fileCombination
in (
not $ null duplicateFilePaths,
"duplicate file-paths have been specified; " ++ show duplicateFilePaths
)
]
-- | A constant empty instance.
nullFileCombination :: FileCombination
nullFileCombination = MkFileCombination 0 []
-- | Smart constructor.
mkFileCombination :: Data.File.FileSize -> Data.File.FilePathList -> FileCombination
mkFileCombination fileSize filePathList
| ToolShed.SelfValidate.isValid fileCombination = fileCombination
| otherwise = error $ "Squeeze.Data.FileCombination.mkFileCombination:\t" ++ ToolShed.SelfValidate.getFirstError fileCombination
where
fileCombination = MkFileCombination fileSize filePathList
-- | Construct a 'FileCombination' from a single 'Data.File.FileSizeAndPath'.
singleton :: Data.File.FileSizeAndPath -> FileCombination
singleton (fileSize, filePath) = mkFileCombination fileSize [filePath]
{- |
* Prepend a 'Data.File.FileSizeAndPath' to an existing 'FileCombination'.
* CAVEAT: performance hot-spot, so it by-passes the checks made by 'mkFileCombination'.
-}
{-# INLINE prepend #-}
prepend
:: Data.File.FileSizeAndPath -- ^ The new path to prepend to the incumbent file-combination.
-> FileCombination -- ^ The incumbent combination of files.
-> FileCombination
prepend (fileSize, filePath) MkFileCombination {
getAggregateFileSize = aggregateFileSize,
getFilePathList = filePathList
} = MkFileCombination {
getAggregateFileSize = fileSize + aggregateFileSize,
getFilePathList = filePath : filePathList
}
-- | Predicate used to determine whether a specific file-combination matches a size-related requirement.
{-# INLINE hasSizeBy #-}
hasSizeBy
:: (Data.File.FileSize -> Bool) -- ^ The predicate.
-> FileCombination -- ^ The input datum to be tested.
-> Bool
hasSizeBy predicate MkFileCombination { getAggregateFileSize = aggregateFileSize } = predicate aggregateFileSize
-- | Progressively raises the selection-criterion as each match is found, to produce monotonically increasing file-combinations.
risingFilter
:: Data.File.FileSize -- ^ The initial minimum byte-size of file to accept.
-> [FileCombination] -- ^ The input list of files to filter.
-> [FileCombination] -- ^ The resulting list of files, which have met rising criterion.
risingFilter _ [] = []
risingFilter minimumSize (fileCombination@MkFileCombination { getAggregateFileSize = aggregateFileSize } : fileCombinations)
| aggregateFileSize >= minimumSize = fileCombination : risingFilter aggregateFileSize fileCombinations
| otherwise = risingFilter minimumSize fileCombinations
{- |
* Merges two lists of monotonically increasing values, into a single monotonically increasing list, by dropping values which compare less than results already found.
* CAVEAT: both lists must produce an element, in order to determine which is selected.
As a result, though one list produces a value, it can't be returned until the other does (which make take a long time), even if ultimately the first is then selected.
-}
risingMerge
:: (FileCombination -> FileCombination -> Ordering) -- ^ Comparator used to select the best file-combination from the heads of the two list supplied.
-> [FileCombination] -- ^ A list of monotonically increasing file-combinations.
-> [FileCombination] -- ^ A list of monotonically increasing file-combinations.
-> [FileCombination]
risingMerge cmp = slave nullFileCombination where
lessThan bar = (== LT) . (`cmp` bar)
slave bar [] r = dropWhile (lessThan bar) r
slave bar l [] = dropWhile (lessThan bar) l
slave _ (x : xs) (y : ys) = case x `cmp` y of
GT -> x : slave x xs ys
LT -> y : slave y xs ys
_ -> x : y : slave x xs ys
-- | Compares two /file-combination/s by their aggregate file-size.
comparingAggregateFileSize :: FileCombination -> FileCombination -> Ordering
comparingAggregateFileSize = Data.Ord.comparing getAggregateFileSize
{- |
Merges two lists of monotonically increasing lists of file-combinations,
into a single monotonically increasing list,
by dropping values which have a smaller aggregate size than results already found.
-}
risingMergeByAggregateFileSize
:: [FileCombination] -- ^ A list of monotonically increasing file-combinations.
-> [FileCombination] -- ^ A list of monotonically increasing file-combinations.
-> [FileCombination]
risingMergeByAggregateFileSize = risingMerge comparingAggregateFileSize