diff options
author | Notkea | 2020-01-10 22:31:47 +0100 |
---|---|---|
committer | GitHub | 2020-01-10 22:31:47 +0100 |
commit | 7042ffc06326fa8ffe70f5a59747709250166c16 (patch) | |
tree | dbfc7567bd106e03a47b499d2a07cecb6b8d6305 /compiler/src/Resource.hs | |
parent | c9264b0a0a7e1cb92ef7d9a391cee2c94376cff3 (diff) | |
parent | 27b51018525dbb7a6edb3073819d82245387ddd3 (diff) | |
download | ldgallery-7042ffc06326fa8ffe70f5a59747709250166c16.tar.gz |
Merge pull request #34 from pacien/develop
first working prototype
Diffstat (limited to 'compiler/src/Resource.hs')
-rw-r--r-- | compiler/src/Resource.hs | 198 |
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 | |||
19 | module Resource | ||
20 | ( ItemProcessor, ThumbnailProcessor | ||
21 | , GalleryItem(..), GalleryItemProps(..), Resolution(..) | ||
22 | , buildGalleryTree, galleryCleanupResourceDir | ||
23 | ) where | ||
24 | |||
25 | |||
26 | import Control.Concurrent.ParallelIO.Global (parallel) | ||
27 | import Data.List ((\\), sortBy) | ||
28 | import Data.Ord (comparing) | ||
29 | import Data.Char (toLower) | ||
30 | import Data.Maybe (mapMaybe, fromMaybe, maybeToList) | ||
31 | import Data.Function ((&)) | ||
32 | import qualified Data.Set as Set | ||
33 | import Data.Time.Clock (UTCTime) | ||
34 | import Data.Time.LocalTime (ZonedTime, utc, utcToZonedTime, zonedTimeToUTC) | ||
35 | import Safe.Foldable (maximumByMay) | ||
36 | |||
37 | import GHC.Generics (Generic) | ||
38 | import Data.Aeson (FromJSON, ToJSON, genericToJSON, genericToEncoding) | ||
39 | import qualified Data.Aeson as JSON | ||
40 | |||
41 | import Files | ||
42 | import Input (InputTree(..), Sidecar(..)) | ||
43 | |||
44 | |||
45 | encodingOptions :: JSON.Options | ||
46 | encodingOptions = 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 | |||
56 | type Tag = String | ||
57 | |||
58 | data Resolution = Resolution | ||
59 | { width :: Int | ||
60 | , height :: Int | ||
61 | } deriving (Generic, Show, FromJSON) | ||
62 | |||
63 | instance ToJSON Resolution where | ||
64 | toJSON = genericToJSON encodingOptions | ||
65 | toEncoding = genericToEncoding encodingOptions | ||
66 | |||
67 | |||
68 | data GalleryItemProps = | ||
69 | Directory { items :: [GalleryItem] } | ||
70 | | Picture { resource :: Path } | ||
71 | | Other { resource :: Path } | ||
72 | deriving (Generic, Show) | ||
73 | |||
74 | instance ToJSON GalleryItemProps where | ||
75 | toJSON = genericToJSON encodingOptions | ||
76 | toEncoding = genericToEncoding encodingOptions | ||
77 | |||
78 | |||
79 | data 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 | |||
89 | instance ToJSON GalleryItem where | ||
90 | toJSON = genericToJSON encodingOptions | ||
91 | toEncoding = genericToEncoding encodingOptions | ||
92 | |||
93 | |||
94 | type ItemProcessor = Path -> IO GalleryItemProps | ||
95 | type ThumbnailProcessor = Path -> IO (Maybe Path) | ||
96 | |||
97 | |||
98 | buildGalleryTree :: | ||
99 | ItemProcessor -> ThumbnailProcessor | ||
100 | -> Int -> String -> InputTree -> IO GalleryItem | ||
101 | buildGalleryTree 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 | |||
163 | flattenGalleryTree :: GalleryItem -> [GalleryItem] | ||
164 | flattenGalleryTree dir@(GalleryItem _ _ _ _ _ _ (Directory items)) = | ||
165 | dir : concatMap flattenGalleryTree items | ||
166 | flattenGalleryTree simple = [simple] | ||
167 | |||
168 | |||
169 | galleryOutputDiff :: GalleryItem -> FSNode -> [Path] | ||
170 | galleryOutputDiff 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 | |||
192 | galleryCleanupResourceDir :: GalleryItem -> FileName -> IO () | ||
193 | galleryCleanupResourceDir 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 | ||