1 -- This file is part of Diohsc
2 -- Copyright (C) 2020 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/.
12 {-# LANGUAGE FlexibleContexts #-}
13 {-# LANGUAGE LambdaCase #-}
14 {-# LANGUAGE OverloadedStrings #-}
15 {-# LANGUAGE TupleSections #-}
19 import Control
.Monad
.Catch
20 import Control
.Monad
.State
22 import Data
.Hashable
(hash
)
24 import Data
.Semigroup
(Any
(..))
25 import qualified Data
.Set
as S
26 import qualified Data
.Text
.Lazy
as T
27 import qualified Data
.Text
.Lazy
.IO as T
29 import qualified System
.Console
.Haskeline
as HL
30 import System
.Directory
31 import System
.Environment
33 import System
.FilePath
35 import System
.IO.Unsafe
(unsafeInterleaveIO
)
38 import qualified BStack
47 import Prompt
hiding (promptYN
)
48 import qualified Prompt
53 import System
.Posix
.Files
(ownerModes
, setFileMode
)
59 (opts
,args
) <- parseArgs argv
60 when (Help `
elem` opts
) $ putStr usage
>> exitSuccess
61 when (Version `
elem` opts
) $ putStrLn version
>> exitSuccess
63 defUserDataDir
<- getAppUserDataDirectory programName
64 userDataDir
<- canonicalizePath
. fromMaybe defUserDataDir
$ listToMaybe [ path | DataDir path
<- opts
]
65 let restrictedMode
= Restricted `
elem` opts
67 outTerm
<- hIsTerminalDevice
stdout
68 let ansi
= NoAnsi `
notElem` opts
&& (outTerm || Ansi `
elem` opts
)
70 let argCommands
:: Opt
-> IO (Any
, [String])
71 argCommands
(ScriptFile
"-") = ((Any
True,) <$>) . warnIOErrAlt
$
72 (T
.unpack
. T
.strip
<$>) . T
.lines <$> T
.getContents
73 argCommands
(ScriptFile f
) = ((Any
True,) <$>) . warnIOErrAlt
$
74 (T
.unpack
<$>) <$> readFileLines f
75 argCommands
(OptCommand c
) = return (Any
True, [c
])
76 argCommands _
= return (Any
False, [])
77 (Any commandsInOpts
, optCommands
) <- mconcat
<$> mapM argCommands opts
78 let repl
= (not commandsInOpts
&& Batch `
notElem` opts
) || Prompt `
elem` opts
79 let interactive
= Batch `
notElem` opts
&& (repl || Interactive `
elem` opts
)
81 let argToUri arg
= doesPathExist arg
>>= \case
82 True -> Just
. ("file://" <>) . escapePathString
<$> makeAbsolute arg
83 False | Just uri
<- parseUriAsAbsolute
. escapeIRI
$ arg
-> return $ Just
$ show uri
84 _
-> printErrFancy ansi
("No such URI / file: " <> arg
) >> return Nothing
85 argCommand
<- join <$> mapM argToUri
(listToMaybe args
)
87 let initialCommands
= optCommands
++ maybeToList argCommand
89 let ghost
= Ghost `
elem` opts
94 setFileMode userDataDir ownerModes
-- chmod 700
97 let cmdHistoryPath
= userDataDir
</> "commandHistory"
98 marksPath
= userDataDir
</> "marks"
99 logPath
= userDataDir
</> "log"
101 let displayInfo
:: [String] -> IO ()
102 displayInfo
= mapM_ $ printInfoFancy ansi
103 displayWarning
= mapM_ $ printErrFancy ansi
104 promptYN
= Prompt
.promptYN interactive
105 callbacks
= InteractionCallbacks displayInfo displayWarning waitKey promptYN
106 socksProxy
= maybe (const NoSocksProxy
) Socks5Proxy
107 (listToMaybe [ h | SocksHost h
<- opts
])
108 . fromMaybe "1080" $ listToMaybe [ p | SocksPort p
<- opts
]
110 requestContext
<- unsafeInterleaveIO
$ initRequestContext callbacks userDataDir ghost socksProxy
111 marks
<- unsafeInterleaveIO
$ loadMarks marksPath
112 let hlSettings
= (HL
.defaultSettings
::HL
.Settings ClientM
)
113 { HL
.complete
= HL
.noCompletion
114 , HL
.historyFile
= if ghost
then Nothing
else Just cmdHistoryPath
117 logLines
<- reverse <$> readFileLines logPath
118 let cLog
= BStack
.fromList logLines
119 let visited
= S
.fromList
$ hash
<$> logLines
121 let openLog
:: IO (Maybe Handle)
122 openLog
= ignoreIOErrAlt
$ Just
<$> do
123 h
<- openFile logPath AppendMode
124 hSetBuffering h LineBuffering
126 closeLog
:: Maybe Handle -> IO ()
127 closeLog
= maybe (return ()) hClose
129 (if ghost
then ($ Nothing
) else bracketOnError openLog closeLog
) $ \logH
->
130 let clientOptions
= ClientOptions userDataDir interactive ansi ghost
131 restrictedMode requestContext logH
132 initState
= emptyClientState
{clientMarks
= marks
133 , clientLog
= cLog
, clientVisited
= visited
}
135 endState
<- (`execStateT` initState
) . HL
.runInputT hlSettings
$
136 lineClient clientOptions initialCommands repl
138 let maxlen
= clientConfMaxLogLen
$ clientConfig endState
139 curlen
= BStack
.size
$ clientLog endState
140 when (not ghost
&& curlen
> maxlen
+ maxlen `
div`
10) . warnIOErr
$
141 -- |reread file rather than just writing clientLog, in case
142 -- another instance has also been appending to the log.
143 truncateToEnd maxlen logPath