Add basic tagging operations to ca-store

This commit is contained in:
hylodon 2025-09-18 22:24:08 +01:00
parent 68bba7c653
commit d3822ad7a6
5 changed files with 98 additions and 4 deletions

1
.gitignore vendored
View file

@ -12,6 +12,7 @@
!src/CAStore/Config/CLI.hs !src/CAStore/Config/CLI.hs
!src/CAStore/Program.hs !src/CAStore/Program.hs
!src/CAStore/Program/Internal.hs !src/CAStore/Program/Internal.hs
!src/CAStore/Program/IO.hs
!src/CAStore/Program/IO/Text.hs !src/CAStore/Program/IO/Text.hs
!src/CAStore/Program/Storage.hs !src/CAStore/Program/Storage.hs
!src/CAStore/Type.hs !src/CAStore/Type.hs

View file

@ -7,8 +7,17 @@ where
import CAStore.Command.Type (Command(..), parseCommand) import CAStore.Command.Type (Command(..), parseCommand)
import CAStore.Program (Program, storeFile, unstoreFile, getFileLocation) import CAStore.Program (Program, storeFile, unstoreFile, getFileLocation)
import CAStore.Program.IO
import CAStore.Program.IO.Text import CAStore.Program.IO.Text
import CAStore.Program.Storage (registerFiles, unregisterFiles, generateId) import CAStore.Program.Storage
( registerFiles
, unregisterFiles
, generateId
, registerTags
, assignTags
, unassignTags
, showTags
)
import CAStore.Type.Text import CAStore.Type.Text
import Control.Monad (forM_) import Control.Monad (forM_)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
@ -18,6 +27,7 @@ runCommand :: Command -> Program ()
runCommand (FileAdd fs) = do runCommand (FileAdd fs) = do
sids <- registerFiles fs sids <- registerFiles fs
forM_ (zip fs sids) $ \(file, sid) -> storeFile file sid forM_ (zip fs sids) $ \(file, sid) -> storeFile file sid
printStoreIds sids
runCommand (FileRemove ss) runCommand (FileRemove ss)
= unregisterFiles ss = unregisterFiles ss
*> forM_ ss unstoreFile *> forM_ ss unstoreFile
@ -30,3 +40,10 @@ runCommand (FileVerify ss) = forM_ ss $ \sid -> do
True -> msg $ MsgArb $ show sid ++ " is valid." True -> msg $ MsgArb $ show sid ++ " is valid."
False -> err $ ErrArb $ show sid ++ " has been corrupted." False -> err $ ErrArb $ show sid ++ " has been corrupted."
False -> err $ ErrArb $ show sid ++ " does not exist in the store." False -> err $ ErrArb $ show sid ++ " does not exist in the store."
runCommand (TagAdd sid ts) = do
registerTags ts
assignTags sid ts
runCommand (TagRemove sid ts) = do
unassignTags sid ts
runCommand (TagShow sid) = do
showTags sid

View file

@ -10,6 +10,9 @@ data Command
= FileAdd [FilePath] = FileAdd [FilePath]
| FileRemove [StoreId] | FileRemove [StoreId]
| FileVerify [StoreId] | FileVerify [StoreId]
| TagAdd StoreId [Tag]
| TagRemove StoreId [Tag]
| TagShow StoreId
deriving (Show) deriving (Show)
parseCommand :: [String] -> Either Error Command parseCommand :: [String] -> Either Error Command
@ -19,4 +22,10 @@ parseCommand ("file":"remove":xs)
= pure $ FileRemove $ StoreId <$> xs = pure $ FileRemove $ StoreId <$> xs
parseCommand ("file":"verify":xs) parseCommand ("file":"verify":xs)
= pure $ FileVerify $ StoreId <$> xs = pure $ FileVerify $ StoreId <$> xs
parseCommand ("tag":"add":x:xs)
= pure $ TagAdd (StoreId x) (Tag <$> xs)
parseCommand ("tag":"remove":x:xs)
= pure $ TagRemove (StoreId x) (Tag <$> xs)
parseCommand ("tag":"show":x:[])
= pure $ TagShow (StoreId x)
parseCommand xs = Left $ ErrInvalidCommand xs parseCommand xs = Left $ ErrInvalidCommand xs

21
src/CAStore/Program/IO.hs Normal file
View file

@ -0,0 +1,21 @@
module CAStore.Program.IO
( printStoreIds
, printTags
)
where
import CAStore.Type
import CAStore.Program.Internal (Program(..))
import Control.Monad.IO.Class (liftIO)
printStoreId :: StoreId -> Program ()
printStoreId (StoreId x) = Program $ liftIO $ putStrLn x
printStoreIds :: [StoreId] -> Program ()
printStoreIds = mapM_ printStoreId
printTag :: Tag -> Program ()
printTag (Tag x) = Program $ liftIO $ putStrLn x
printTags :: [Tag] -> Program ()
printTags = mapM_ printTag

View file

@ -5,11 +5,16 @@ module CAStore.Program.Storage
, generateId , generateId
, registerFiles , registerFiles
, unregisterFiles , unregisterFiles
, registerTags
, unregisterTags
, assignTags
, unassignTags
, showTags
) )
where where
import CAStore.Program.Internal import CAStore.Program.Internal
import CAStore.Type (StoreId(..)) import CAStore.Type
import Control.Monad (unless) import Control.Monad (unless)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
import Crypto.Hash (hash, SHA256) import Crypto.Hash (hash, SHA256)
@ -23,6 +28,10 @@ initialise = do
tables <- getAllDefinedTables tables <- getAllDefinedTables
unless ("file" `elem` tables) $ do unless ("file" `elem` tables) $ do
runSqlQ "CREATE TABLE file (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, hash TEXT NOT NULL UNIQUE, insertion_date TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP CHECK (insertion_date LIKE '____-__-__ __:__:__')) STRICT" runSqlQ "CREATE TABLE file (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, hash TEXT NOT NULL UNIQUE, insertion_date TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP CHECK (insertion_date LIKE '____-__-__ __:__:__')) STRICT"
unless ("tag" `elem` tables) $ do
runSqlQ "CREATE TABLE tag (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE) STRICT"
unless ("is_tagged_with" `elem` tables) $ do
runSqlQ "CREATE TABLE is_tagged_with (file INTEGER NOT NULL REFERENCES file(id), tag INTEGER NOT NULL REFERENCES tag(id), PRIMARY KEY (file, tag)) STRICT"
generateId :: FilePath -> Program StoreId generateId :: FilePath -> Program StoreId
generateId = fmap (StoreId . show . hash @SHA256) . liftIO . readFile generateId = fmap (StoreId . show . hash @SHA256) . liftIO . readFile
@ -46,12 +55,49 @@ unregisterFiles ss = do
runSqlQ "DELETE FROM file WHERE hash IN (SELECT * FROM t_unreg_file)" runSqlQ "DELETE FROM file WHERE hash IN (SELECT * FROM t_unreg_file)"
runSqlQ "DELETE FROM t_unreg_file" runSqlQ "DELETE FROM t_unreg_file"
-- runSql :: (Connection -> IO a) -> Program a registerTags :: [Tag] -> Program ()
-- runSql f = getConnectionHandle >>= (liftIO . f) registerTags ts = do
runSqlQ "CREATE TEMP TABLE t_reg_tag (name TEXT NOT NULL) STRICT"
runSqlMany "INSERT INTO t_reg_tag (name) VALUES (?)" ts
runSqlQ "INSERT INTO tag (name) SELECT name FROM t_reg_tag EXCEPT SELECT name FROM tag"
runSqlQ "DELETE FROM t_reg_tag"
unregisterTags :: [Tag] -> Program ()
unregisterTags ts = do
runSqlQ "CREATE TEMP TABLE t_unreg_tag (name TEXT NOT NULL) STRICT"
runSqlMany "INSERT INTO t_unreg_tag (name) VALUES (?)" ts
runSqlQ "DELETE FROM tag WHERE name IN (SELECT * FROM t_unreg_tag)"
runSqlQ "DELETE FROM t_unreg_tag"
assignTags :: StoreId -> [Tag] -> Program ()
assignTags sid ts = do
runSqlQ "CREATE TEMP TABLE t_ass_tag (name TEXT NOT NULL) STRICT"
runSqlMany "INSERT INTO t_ass_tag (name) VALUES (?)" ts
runSqlQP "INSERT OR IGNORE INTO is_tagged_with (file, tag) SELECT (SELECT id FROM file WHERE hash = ?), t.id FROM t_ass_tag AS r INNER JOIN tag AS t ON r.name = t.name" sid
runSqlQ "DELETE FROM t_ass_tag"
unassignTags :: StoreId -> [Tag] -> Program ()
unassignTags sid ts = do
runSqlQ "CREATE TEMP TABLE t_unass_tag (name TEXT NOT NULL) STRICT"
runSqlMany "INSERT INTO t_unass_tag (name) VALUES (?)" ts
runSqlQP "DELETE FROM is_tagged_with WHERE file IN (SELECT id FROM file WHERE hash = ?) AND tag IN (SELECT id FROM tag WHERE name IN (SELECT * FROM t_unass_tag))" sid
runSqlQ "DELETE FROM t_unass_tag"
showTags :: StoreId -> Program ()
showTags sid = do
runSql $ \h ->
fold h "SELECT t.name FROM is_tagged_with AS r INNER JOIN file AS f ON r.file = f.id INNER JOIN tag AS t ON r.tag = t.id WHERE f.hash = ? ORDER BY t.name ASC" (Only sid) ()
(\() (Only x) -> putStrLn x)
runSql :: (Connection -> IO a) -> Program a
runSql f = getConnectionHandle >>= (Program . liftIO . f)
runSqlQ :: Query -> Program () runSqlQ :: Query -> Program ()
runSqlQ q = getConnectionHandle >>= (liftIO . flip execute_ q) runSqlQ q = getConnectionHandle >>= (liftIO . flip execute_ q)
runSqlQP :: ToField a => Query -> a -> Program ()
runSqlQP q x = getConnectionHandle >>= \h -> (liftIO $ execute h q $ Only x)
runSqlMany :: ToField a => Query -> [a] -> Program () runSqlMany :: ToField a => Query -> [a] -> Program ()
runSqlMany q xs = getConnectionHandle >>= \h -> (liftIO $ executeMany h q (Only <$> xs)) runSqlMany q xs = getConnectionHandle >>= \h -> (liftIO $ executeMany h q (Only <$> xs))