1 -- This file is part of htalkat
2 -- Copyright (C) 2021 Martin Bays <mbays@sdf.org>
4 -- This program is free software: you can redistribute it and/or modify
5 -- it under the terms of version 3 of the GNU General Public License as
6 -- published by the Free Software Foundation, or any later version.
8 -- You should have received a copy of the GNU General Public License
9 -- along with this program. If not, see http://www.gnu.org/licenses/.
15 import Control
.Monad
(msum)
16 import Control
.Monad
.Trans
.Maybe (MaybeT
(..), runMaybeT
)
17 import Data
.Char (isAlphaNum)
18 import Data
.List
(elemIndices)
19 import Data
.Maybe (mapMaybe)
20 import Safe
(lastMay
, readMay
)
21 import System
.Directory
(createDirectoryIfMissing
,
23 import System
.FileLock
(SharedExclusive
(..), withFileLock
)
24 import System
.FilePath (isValid
, takeFileName
, (</>))
30 #if !(MIN_VERSION_base
(4,11,0))
34 data Petname
= Named
String | Unnamed
Int
37 instance Ord Petname
where
38 compare (Named _
) (Unnamed _
) = LT
39 compare (Unnamed _
) (Named _
) = GT
40 compare (Named n
) (Named n
')
41 | c
<- compare (length n
) (length n
')
43 compare (Named n
) (Named n
') = compare n n
'
44 compare (Unnamed n
) (Unnamed n
') = compare n n
'
46 parsePetname
:: String -> Maybe Petname
47 parsePetname
('+':s
) | Just n
<- readMay s
, n
> 0 = Just
$ Unnamed n
48 parsePetname s | isValidPetname s
= Just
$ Named s
49 parsePetname _
= Nothing
51 isValidPetname
:: String -> Bool
52 isValidPetname
('.':_
) = False
53 isValidPetname s
= shellQuotable s
&& isValid s
&& s
== takeFileName s
54 where shellQuotable
= notElem '\''
56 showPetname
:: Petname
-> String
57 showPetname
(Named s
) = s
58 showPetname
(Unnamed n
) = '+':show n
60 shellQuotePetname
:: Petname
-> String
61 shellQuotePetname
= shellQuote
. showPetname
where
63 |
all shellSafe s
&& not (null s
) = s
64 |
otherwise = '\'' : s
<> "'"
65 shellSafe c
= isAlphaNum c || c `
elem`
".,_-+="
67 namesDir
:: FilePath -> FilePath
68 namesDir
= (</> "names")
70 lookupName
:: FilePath -> Petname
-> IO (Maybe User
)
71 lookupName ddir name
= do
72 let ndir
= namesDir ddir
73 path
= ndir
</> showPetname name
74 createDirectoryIfMissing
True ndir
75 ignoreIOErrAlt
. withFileLock
(ndir
</> ".lock") Shared
$ \_
->
76 parseUser
<$> readFile path
78 resolveTarget
:: FilePath -> String -> IO (Maybe User
)
79 resolveTarget ddir target
80 | Just user
<- parseUser target
= pure
$ Just user
81 |
otherwise = runMaybeT
$ msum
83 name
<- MaybeT
. pure
$ parsePetname target
84 MaybeT
$ lookupName ddir name
86 i
<- MaybeT
. pure
. lastMay
$ elemIndices '@' target
87 let (n
, '@':h
) = splitAt i target
88 name
<- MaybeT
. pure
$ parsePetname n
89 host
<- MaybeT
. pure
$ parseHost h
90 MaybeT
$ ((\u
-> u
{ userHost
= Just host
}) <$>) <$>
94 writeName
:: FilePath -> User
-> Petname
-> IO ()
95 writeName ddir user name
= do
96 let ndir
= namesDir ddir
97 path
= ndir
</> showPetname name
98 createDirectoryIfMissing
True ndir
99 withFileLock
(ndir
</> ".lock") Exclusive
$ \_
->
100 writeFile path
$ showUser user
102 loadNames
:: FilePath -> IO [Petname
]
103 loadNames ddir
= mapMaybe parsePetname
<$> listDirectory
(namesDir ddir
)