aboutsummaryrefslogtreecommitdiff
path: root/compiler/src/Resource.hs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/src/Resource.hs')
-rw-r--r--compiler/src/Resource.hs198
1 files changed, 198 insertions, 0 deletions
diff --git a/compiler/src/Resource.hs b/compiler/src/Resource.hs
new file mode 100644
index 0000000..56f7a3f
--- /dev/null
+++ b/compiler/src/Resource.hs
@@ -0,0 +1,198 @@
1-- ldgallery - A static generator which turns a collection of tagged
2-- pictures into a searchable web gallery.
3--
4-- Copyright (C) 2019-2020 Pacien TRAN-GIRARD
5--
6-- This program is free software: you can redistribute it and/or modify
7-- it under the terms of the GNU Affero General Public License as
8-- published by the Free Software Foundation, either version 3 of the
9-- License, or (at your option) any later version.
10--
11-- This program is distributed in the hope that it will be useful,
12-- but WITHOUT ANY WARRANTY; without even the implied warranty of
13-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14-- GNU Affero General Public License for more details.
15--
16-- You should have received a copy of the GNU Affero General Public License
17-- along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19module Resource
20 ( ItemProcessor, ThumbnailProcessor
21 , GalleryItem(..), GalleryItemProps(..), Resolution(..)
22 , buildGalleryTree, galleryCleanupResourceDir
23 ) where
24
25
26import Control.Concurrent.ParallelIO.Global (parallel)
27import Data.List ((\\), sortBy)
28import Data.Ord (comparing)
29import Data.Char (toLower)
30import Data.Maybe (mapMaybe, fromMaybe, maybeToList)
31import Data.Function ((&))
32import qualified Data.Set as Set
33import Data.Time.Clock (UTCTime)
34import Data.Time.LocalTime (ZonedTime, utc, utcToZonedTime, zonedTimeToUTC)
35import Safe.Foldable (maximumByMay)
36
37import GHC.Generics (Generic)
38import Data.Aeson (FromJSON, ToJSON, genericToJSON, genericToEncoding)
39import qualified Data.Aeson as JSON
40
41import Files
42import Input (InputTree(..), Sidecar(..))
43
44
45encodingOptions :: JSON.Options
46encodingOptions = JSON.defaultOptions
47 { JSON.fieldLabelModifier = map toLower
48 , JSON.constructorTagModifier = map toLower
49 , JSON.sumEncoding = JSON.defaultTaggedObject
50 { JSON.tagFieldName = "type"
51 , JSON.contentsFieldName = "contents"
52 }
53 }
54
55
56type Tag = String
57
58data Resolution = Resolution
59 { width :: Int
60 , height :: Int
61 } deriving (Generic, Show, FromJSON)
62
63instance ToJSON Resolution where
64 toJSON = genericToJSON encodingOptions
65 toEncoding = genericToEncoding encodingOptions
66
67
68data GalleryItemProps =
69 Directory { items :: [GalleryItem] }
70 | Picture { resource :: Path }
71 | Other { resource :: Path }
72 deriving (Generic, Show)
73
74instance ToJSON GalleryItemProps where
75 toJSON = genericToJSON encodingOptions
76 toEncoding = genericToEncoding encodingOptions
77
78
79data GalleryItem = GalleryItem
80 { title :: String
81 , datetime :: ZonedTime
82 , description :: String
83 , tags :: [Tag]
84 , path :: Path
85 , thumbnail :: Maybe Path
86 , properties :: GalleryItemProps
87 } deriving (Generic, Show)
88
89instance ToJSON GalleryItem where
90 toJSON = genericToJSON encodingOptions
91 toEncoding = genericToEncoding encodingOptions
92
93
94type ItemProcessor = Path -> IO GalleryItemProps
95type ThumbnailProcessor = Path -> IO (Maybe Path)
96
97
98buildGalleryTree ::
99 ItemProcessor -> ThumbnailProcessor
100 -> Int -> String -> InputTree -> IO GalleryItem
101buildGalleryTree processItem processThumbnail tagsFromDirectories galleryName inputTree =
102 mkGalleryItem [] inputTree
103 where
104 mkGalleryItem :: [String] -> InputTree -> IO GalleryItem
105 mkGalleryItem parentTitles InputFile{path, modTime, sidecar} =
106 do
107 properties <- processItem path
108 processedThumbnail <- processThumbnail path
109 return GalleryItem
110 { title = fromMeta title $ fromMaybe "" $ fileName path
111 , datetime = fromMaybe (toZonedTime modTime) (Input.datetime sidecar)
112 , description = fromMeta description ""
113 , tags = unique ((fromMeta tags []) ++ implicitParentTags parentTitles)
114 , path = "/" /> path
115 , thumbnail = processedThumbnail
116 , properties = properties }
117
118 where
119 fromMeta :: (Sidecar -> Maybe a) -> a -> a
120 fromMeta get fallback = fromMaybe fallback $ get sidecar
121
122 mkGalleryItem parentTitles InputDir{path, modTime, dirThumbnailPath, items} =
123 do
124 processedThumbnail <- maybeThumbnail dirThumbnailPath
125 processedItems <- parallel $ map (mkGalleryItem subItemsParents) items
126 return GalleryItem
127 { title = fromMaybe galleryName (fileName path)
128 , datetime = fromMaybe (toZonedTime modTime) (mostRecentModTime processedItems)
129 , description = ""
130 , tags = unique (aggregateTags processedItems ++ implicitParentTags parentTitles)
131 , path = "/" /> path
132 , thumbnail = processedThumbnail
133 , properties = Directory processedItems }
134
135 where
136 subItemsParents :: [String]
137 subItemsParents = (maybeToList $ fileName path) ++ parentTitles
138
139 maybeThumbnail :: Maybe Path -> IO (Maybe Path)
140 maybeThumbnail Nothing = return Nothing
141 maybeThumbnail (Just thumbnailPath) = processThumbnail thumbnailPath
142
143 mostRecentModTime :: [GalleryItem] -> Maybe ZonedTime
144 mostRecentModTime =
145 maximumByMay comparingTime . map (datetime::(GalleryItem -> ZonedTime))
146
147 comparingTime :: ZonedTime -> ZonedTime -> Ordering
148 comparingTime l r = compare (zonedTimeToUTC l) (zonedTimeToUTC r)
149
150 aggregateTags :: [GalleryItem] -> [Tag]
151 aggregateTags = concatMap (\item -> tags (item::GalleryItem))
152
153 unique :: Ord a => [a] -> [a]
154 unique = Set.toList . Set.fromList
155
156 implicitParentTags :: [String] -> [Tag]
157 implicitParentTags = take tagsFromDirectories
158
159 toZonedTime :: UTCTime -> ZonedTime
160 toZonedTime = utcToZonedTime utc
161
162
163flattenGalleryTree :: GalleryItem -> [GalleryItem]
164flattenGalleryTree dir@(GalleryItem _ _ _ _ _ _ (Directory items)) =
165 dir : concatMap flattenGalleryTree items
166flattenGalleryTree simple = [simple]
167
168
169galleryOutputDiff :: GalleryItem -> FSNode -> [Path]
170galleryOutputDiff resources ref =
171 (filesystemPaths ref) \\ (compiledPaths $ flattenGalleryTree resources)
172 where
173 filesystemPaths :: FSNode -> [Path]
174 filesystemPaths = map Files.path . tail . flattenDir
175
176 compiledPaths :: [GalleryItem] -> [Path]
177 compiledPaths items =
178 resourcePaths items ++ thumbnailPaths items
179 & concatMap subPaths
180
181 resourcePaths :: [GalleryItem] -> [Path]
182 resourcePaths = mapMaybe (resourcePath . properties)
183
184 resourcePath :: GalleryItemProps -> Maybe Path
185 resourcePath Directory{} = Nothing
186 resourcePath resourceProps = Just $ resource resourceProps
187
188 thumbnailPaths :: [GalleryItem] -> [Path]
189 thumbnailPaths = mapMaybe thumbnail
190
191
192galleryCleanupResourceDir :: GalleryItem -> FileName -> IO ()
193galleryCleanupResourceDir resourceTree outputDir =
194 readDirectory outputDir
195 >>= return . galleryOutputDiff resourceTree . root
196 >>= return . sortBy (flip $ comparing pathLength) -- nested files before dirs
197 >>= return . map (localPath . (/>) outputDir)
198 >>= mapM_ remove