Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- Added test cases for the saveGraphJSON function in `Controllers/Graph`
- Added test cases for the getGraphJSON function in `Controllers/Graph`
- Fix unused variable from `Graph.js`, formatting in `Container.js` and `GraphDropdown.js`, and eslint config
- Refactor `graphImageResponse` in `Controllers/Graph` to use temporary files

## [0.7.2] - 2025-12-10

Expand Down
14 changes: 10 additions & 4 deletions app/Controllers/Graph.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ import Data.Maybe (fromMaybe)
import Happstack.Server (Response, ServerPart, look, lookBS, lookText', ok, toResponse)
import MasterTemplate (masterTemplate)
import Scripts (graphScripts)
import System.IO (hClose)
import System.IO.Temp (withSystemTempFile)
import Text.Blaze ((!))
import qualified Text.Blaze.Html5 as H
import qualified Text.Blaze.Html5.Attributes as A

import Config (runDb)
import Database.Persist.Sqlite (Entity, SelectOpt (Asc), SqlPersistM, selectList, (==.))
import Database.Tables as Tables (EntityField (GraphDynamic, GraphTitle), Graph, SvgJSON, Text)
import Export.GetImages (getActiveGraphImage)
import Export.GetImages (writeActiveGraphImage)
import Models.Graph (getGraph, insertGraph)
import Util.Happstack (createJSONResponse)
import Util.Helpers (returnImageData)
import Util.Helpers (readImageData)

graphResponse :: ServerPart Response
graphResponse =
Expand Down Expand Up @@ -51,8 +53,12 @@ getGraphJSON = do
graphImageResponse :: ServerPart Response
graphImageResponse = do
graphInfo <- look "JsonLocalStorageObj"
(svgFilename, imageFilename) <- liftIO $ getActiveGraphImage graphInfo
liftIO $ returnImageData svgFilename imageFilename
liftIO $ withSystemTempFile "graph.svg" $ \svgPath svgHandle -> do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the filenames should still be random (want to avoid possible name collisions if multiple responses are being executed at the same time)

hClose svgHandle
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to use this handle, passing it down all the way to buildSVGHelper in order to write directly to the file. This is different from the png temp file, since for the latter the file is being created externally to Courseography (by running magick in a subprocess)

withSystemTempFile "graph.png" $ \pngPath pngHandle -> do
hClose pngHandle
writeActiveGraphImage graphInfo svgPath pngPath
readImageData pngPath

-- | Inserts SVG graph data into Texts, Shapes, and Paths tables
saveGraphJSON :: ServerPart Response
Expand Down
37 changes: 19 additions & 18 deletions app/Export/GetImages.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Defines functions for creating images from graphs and timetables, most
functions return the name of the created svg and png files after creation.
-}
module Export.GetImages
(getActiveGraphImage, getTimetableImage, randomName, getActiveTimetable) where
(getActiveGraphImage, getTimetableImage, randomName, getActiveTimetable, writeActiveGraphImage) where

import Config (runDb)
import Data.Aeson (decode)
Expand All @@ -24,16 +24,22 @@ import Models.Meeting (getMeetingTime)
import Svg.Generator
import System.Random (genWord32, newStdGen)


-- | If there is an active graph available, an image of that graph is created,
-- otherwise the Computer Science graph is created as a default.
-- Either way, the resulting graph's .svg and .png names are returned.
getActiveGraphImage :: String -> IO (String, String)
getActiveGraphImage graphInfo = do
-- | If there is an active graph available, an image of the active graph is written,
-- otherwise the Computer Science graph is written as a default.
writeActiveGraphImage :: String -> FilePath -> FilePath -> IO ()
writeActiveGraphImage graphInfo svgPath pngPath = do
let graphInfoMap = fromMaybe M.empty $ decode $ fromStrict $ BC.pack graphInfo :: M.Map T.Text T.Text
graphName = fromMaybe "Computer-Science" $ M.lookup "active-graph" graphInfoMap
getGraphImage graphName graphInfoMap
getGraphImage graphName graphInfoMap svgPath pngPath

-- | Creates an image of the graph given info and returns resulting graph's .svg and .png names.
getActiveGraphImage :: String -> IO (String, String)
getActiveGraphImage graphInfo = do
rand <- randomName
let svgFilename = rand ++ ".svg"
imageFilename = rand ++ ".png"
writeActiveGraphImage graphInfo svgFilename imageFilename
return (svgFilename, imageFilename)

-- | If there are selected lectures available, an timetable image of
-- those lectures in specified session is created.
Expand Down Expand Up @@ -107,16 +113,11 @@ generateTimetableImg schedule courseSession = do
createImageFile svgFilename imageFilename
return (svgFilename, imageFilename)

-- | Creates an image, and returns the name of the svg used to create the
-- image and the name of the image
getGraphImage :: T.Text -> M.Map T.Text T.Text -> IO (String, String)
getGraphImage graphName courseMap = do
rand <- randomName
let svgFilename = rand ++ ".svg"
imageFilename = rand ++ ".png"
buildSVG graphName courseMap svgFilename True
createImageFile svgFilename imageFilename
return (svgFilename, imageFilename)
-- | Creates an image, given file paths to the svg and image to write to
getGraphImage :: T.Text -> M.Map T.Text T.Text -> FilePath -> FilePath -> IO ()
getGraphImage graphName courseMap svgPath pngPath = do
buildSVG graphName courseMap svgPath True
createImageFile svgPath pngPath

-- | Creates an image, and returns the name of the svg used to create the
-- image and the name of the image
Expand Down
14 changes: 10 additions & 4 deletions app/Util/Helpers.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Description : Contains general-use helper functions.
-}
module Util.Helpers
(safeHead, returnImageData) where
(safeHead, readImageData, returnImageData) where

import qualified Data.ByteString as BS (readFile)
import qualified Data.ByteString.Base64 as BEnc (encode)
Expand All @@ -16,12 +16,18 @@ safeHead :: a -> [a] -> a
safeHead listHead [] = listHead
safeHead _ (listHead:_) = listHead

-- | Reads the data in an image file and returns the data as a response.
readImageData :: String -> IO Response
readImageData imageFileName = do
imageData <- BS.readFile imageFileName
let encodedData = BEnc.encode imageData
return $ toResponse encodedData

-- | Creates and converts an SVG file to an image file, deletes them both and
-- returns the image data as a response.
returnImageData :: String -> String -> IO Response
returnImageData svgFilename imageFilename = do
imageData <- BS.readFile imageFilename
response <- readImageData imageFilename
_ <- removeFile imageFilename
_ <- removeFile svgFilename
let encodedData = BEnc.encode imageData
return $ toResponse encodedData
return response
2 changes: 2 additions & 0 deletions courseography.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ library
split,
system-filepath,
tagsoup,
temporary,
text,
time,
tls,
Expand Down Expand Up @@ -271,6 +272,7 @@ executable courseography
stylish-haskell,
system-filepath,
tagsoup,
temporary,
text,
time,
tls,
Expand Down