summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Truzjan <pxqr.sta@gmail.com>2014-01-05 00:01:20 +0400
committerSam Truzjan <pxqr.sta@gmail.com>2014-01-05 00:01:20 +0400
commit980b65c6cbe5d02ab6479ffb76783178ace0575d (patch)
tree66669331ecac59d1f2b0f6b4175c3dccf70932e9
parent9d691a89ba079805d2823a736ec50c0c2caaca5a (diff)
Move mktorrent sources to this pkg
-rw-r--r--bittorrent.cabal37
-rw-r--r--examples/MkTorrent.hs389
2 files changed, 426 insertions, 0 deletions
diff --git a/bittorrent.cabal b/bittorrent.cabal
index 41782605..2f30d848 100644
--- a/bittorrent.cabal
+++ b/bittorrent.cabal
@@ -241,6 +241,42 @@ test-suite spec
241-- , bittorrent 241-- , bittorrent
242-- ghc-options: -O2 -Wall -fno-warn-orphans 242-- ghc-options: -O2 -Wall -fno-warn-orphans
243 243
244-- Utility to work with torrent files.
245executable mktorrent
246 if !flag(examples)
247 buildable: False
248 default-language: Haskell2010
249 hs-source-dirs: examples
250 main-is: MkTorrent.hs
251 other-modules: Paths_bittorrent
252-- , MkTorrent.Amend
253-- , MkTorrent.Check
254-- , MkTorrent.Create
255-- , MkTorrent.Magnet
256-- , MkTorrent.Show
257 build-depends: base == 4.6.*
258 , lens
259 , time
260
261 , bytestring
262
263 , stm
264 , async
265 , parallel
266
267 , text
268 , pretty
269 , pretty-class
270 , network
271 , bittorrent
272
273 , optparse-applicative
274 , directory
275 , filepath
276 , hslogger
277 ghc-options: -Wall -threaded
278
279-- Utility to fetch
244executable gettorrent 280executable gettorrent
245 if !flag(examples) 281 if !flag(examples)
246 buildable: False 282 buildable: False
@@ -256,6 +292,7 @@ executable gettorrent
256 , filepath 292 , filepath
257 , bittorrent 293 , bittorrent
258 294
295-- nonfunctioning example of very basic bittorrent client
259executable client 296executable client
260 if !flag(examples) 297 if !flag(examples)
261 buildable: False 298 buildable: False
diff --git a/examples/MkTorrent.hs b/examples/MkTorrent.hs
new file mode 100644
index 00000000..ca7f5942
--- /dev/null
+++ b/examples/MkTorrent.hs
@@ -0,0 +1,389 @@
1{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE RecordWildCards #-}
3{-# LANGUAGE StandaloneDeriving #-}
4{-# OPTIONS -fno-warn-orphans #-}
5module Main (main) where
6
7import Prelude as P
8import Control.Exception
9import Control.Lens hiding (argument)
10import Control.Monad
11import Data.List as L
12import Data.Monoid
13import Data.Text as T
14import qualified Data.Text.IO as T
15import Data.Text.Read as T
16import Data.Version
17import Network.URI
18import Options.Applicative
19import System.Log
20import System.Log.Logger
21import System.Exit
22import Text.Read
23import Text.PrettyPrint.Class
24
25import Paths_bittorrent (version)
26import Data.Torrent
27import Data.Torrent.Magnet hiding (Magnet (Magnet))
28import Data.Torrent.Magnet (Magnet)
29
30--import MkTorrent.Check
31--import MkTorrent.Create
32
33{-----------------------------------------------------------------------
34-- Dialogs
35-----------------------------------------------------------------------}
36
37instance Read URI where
38 readsPrec _ = f . parseURI
39 where
40 f Nothing = []
41 f (Just u) = [(u, "")]
42
43question :: Show a => Text -> Maybe a -> IO ()
44question q def = do
45 T.putStrLn q
46 case def of
47 Nothing -> return ()
48 Just v -> T.putStrLn $ "[default: " <> T.pack (show v) <> "]"
49
50ask :: Read a => Text -> IO a
51ask q = question q (Just True) >> getReply
52 where
53 getReply = do
54 resp <- P.getLine
55 maybe getReply return $ readMaybe resp
56
57askMaybe :: Read a => Text -> IO (Maybe a)
58askMaybe q = question q (Just False) >> getReply
59 where
60 getReply = do
61 resp <- P.getLine
62 if resp == []
63 then return Nothing
64 else maybe getReply return $ readMaybe resp
65
66askURI :: IO URI
67askURI = do
68 str <- P.getLine
69 case parseURI str of
70 Nothing -> T.putStrLn "incorrect URI" >> askURI
71 Just u -> return u
72
73askFreeform :: IO Text
74askFreeform = do
75 str <- T.getLine
76 if T.null str
77 then askFreeform
78 else return str
79
80askInRange :: Int -> Int -> IO Int
81askInRange a b = do
82 str <- T.getLine
83 case T.decimal str of
84 Left msg -> do
85 P.putStrLn msg
86 askInRange a b
87 Right (i, _)
88 | a <= i && i < b -> return i
89 | otherwise -> do
90 T.putStrLn "not in range "
91 askInRange a b
92
93askChoice :: [(Text, a)] -> IO a
94askChoice kvs = do
95 forM_ (L.zip [1 :: Int ..] $ L.map fst kvs) $ \(i, lbl) -> do
96 T.putStrLn $ " " <> T.pack (show i) <> ") " <> lbl
97 T.putStrLn "Your choice?"
98 ix <- askInRange 1 (succ (L.length kvs))
99 return $ snd (kvs !! pred ix)
100
101{-----------------------------------------------------------------------
102-- Helpers
103-----------------------------------------------------------------------}
104
105torrentFile :: Parser FilePath
106torrentFile = argument Just
107 ( metavar "FILE"
108 <> help "A .torrent file"
109 )
110
111{-----------------------------------------------------------------------
112-- Amend command
113-----------------------------------------------------------------------}
114
115data AmendOpts = AmendOpts FilePath
116 deriving Show
117
118amendOpts :: Parser AmendOpts
119amendOpts = AmendOpts <$> torrentFile
120
121amendInfo :: ParserInfo AmendOpts
122amendInfo = info (helper <*> amendOpts) modifier
123 where
124 modifier = progDesc "Edit info fields of existing torrent"
125
126type Amend = Torrent -> Torrent
127
128fields :: [(Text, IO Amend)]
129fields = [ ("announce", set announce <$> askURI)
130 , ("comment", set comment . Just <$> askFreeform)
131 , ("created by", set createdBy . Just <$> askFreeform)
132 , ("publisher url", set publisherURL . Just <$> askURI)
133 ]
134
135askAmend :: IO Amend
136askAmend = join $ T.putStrLn "Choose a field:" >> askChoice fields
137
138amend :: AmendOpts -> IO Bool
139amend (AmendOpts tpath) = do
140 t <- fromFile tpath
141 a <- askAmend
142 toFile tpath $ a t
143 return True
144
145{-----------------------------------------------------------------------
146-- Check command
147-----------------------------------------------------------------------}
148
149{-
150checkOpts :: Parser CheckOpts
151checkOpts = CheckOpts
152 <$> torrentFile
153 <*> argument Just
154 ( metavar "PATH"
155 <> value "."
156 <> help "Content directory or a single file" )
157
158checkInfo :: ParserInfo CheckOpts
159checkInfo = info (helper <*> checkOpts) modifier
160 where
161 modifier = progDesc "Validate integrity of torrent data"
162
163{-----------------------------------------------------------------------
164-- Create command
165-----------------------------------------------------------------------}
166
167createFlags :: Parser CreateFlags
168createFlags = CreateFlags
169 <$> optional (option
170 ( long "piece-size"
171 <> short 's'
172 <> metavar "SIZE"
173 <> help "Set size of torrent pieces"
174 ))
175 <*> switch
176 ( long "md5"
177 <> short '5'
178 <> help "Include md5 hash of each file"
179 )
180 <*> switch
181 ( long "ignore-dot-files"
182 <> short 'd'
183 <> help "Do not include .* files"
184 )
185
186
187createOpts :: Parser CreateOpts
188createOpts = CreateOpts
189 <$> argument Just
190 ( metavar "PATH"
191 <> help "Content directory or a single file"
192 )
193 <*> optional (argument Just
194 ( metavar "FILE"
195 <> help "Place for the output .torrent file"
196 ))
197 <*> createFlags
198
199createInfo :: ParserInfo CreateOpts
200createInfo = info (helper <*> createOpts) modifier
201 where
202 modifier = progDesc "Make a new .torrent file"
203-}
204
205{-----------------------------------------------------------------------
206-- Magnet command
207-----------------------------------------------------------------------}
208
209data MagnetFlags = MagnetFlags
210 { detailed :: Bool
211 } deriving Show
212
213data MagnetOpts = MagnetOpts FilePath MagnetFlags
214 deriving Show
215
216magnetFlags :: Parser MagnetFlags
217magnetFlags = MagnetFlags
218 <$> switch
219 ( long "detailed"
220 )
221
222magnetOpts :: Parser MagnetOpts
223magnetOpts = MagnetOpts <$> torrentFile <*> magnetFlags
224
225magnetInfo :: ParserInfo MagnetOpts
226magnetInfo = info (helper <*> magnetOpts) modifier
227 where
228 modifier = progDesc "Print magnet link"
229
230mkMagnet :: MagnetFlags -> Torrent -> Magnet
231mkMagnet MagnetFlags {..} = if detailed then detailedMagnet else simpleMagnet
232
233magnet :: MagnetOpts -> IO Bool
234magnet (MagnetOpts tpath flags) = do
235 print . mkMagnet flags =<< fromFile tpath
236 return True
237
238{-----------------------------------------------------------------------
239-- Show command
240-----------------------------------------------------------------------}
241
242data ShowFlags = ShowFlags
243 { infoHashOnly :: Bool
244 } deriving Show
245
246data ShowOpts = ShowOpts FilePath ShowFlags
247 deriving Show
248
249showFlags :: Parser ShowFlags
250showFlags = ShowFlags
251 <$> switch
252 ( long "infohash"
253 <> help "Show only hash of the torrent info part"
254 )
255
256showOpts :: Parser ShowOpts
257showOpts = ShowOpts <$> torrentFile <*> showFlags
258
259showInfo :: ParserInfo ShowOpts
260showInfo = info (helper <*> showOpts) modifier
261 where
262 modifier = progDesc "Print .torrent file metadata"
263
264showTorrent :: ShowFlags -> Torrent -> ShowS
265showTorrent ShowFlags {..} torrent
266 | infoHashOnly = shows $ idInfoHash (tInfoDict torrent)
267 | otherwise = shows $ pretty torrent
268
269putTorrent :: ShowOpts -> IO Bool
270putTorrent (ShowOpts torrentPath flags) = do
271 torrent <- fromFile torrentPath `onException` putStrLn help
272 putStrLn $ showTorrent flags torrent []
273 return True
274 where
275 help = "Most likely this is not a valid .torrent file"
276
277{-----------------------------------------------------------------------
278-- Command
279-----------------------------------------------------------------------}
280
281data Command
282 = Amend AmendOpts
283-- | Check CheckOpts
284-- | Create CreateOpts
285 | Magnet MagnetOpts
286 | Show ShowOpts
287 deriving Show
288
289commandOpts :: Parser Command
290commandOpts = subparser $ mconcat
291 [ command "amend" (Amend <$> amendInfo)
292-- , command "check" (Check <$> checkInfo)
293-- , command "create" (Create <$> createInfo)
294 , command "magnet" (Magnet <$> magnetInfo)
295 , command "show" (Show <$> showInfo)
296 ]
297
298{-----------------------------------------------------------------------
299-- Global Options
300-----------------------------------------------------------------------}
301
302data GlobalOpts = GlobalOpts
303 { verbosity :: Priority
304 } deriving Show
305
306deriving instance Enum Priority
307deriving instance Bounded Priority
308
309priorities :: [Priority]
310priorities = [minBound..maxBound]
311
312defaultPriority :: Priority
313defaultPriority = WARNING
314
315verbosityOpts :: Parser Priority
316verbosityOpts = verbosityP <|> verboseP <|> quietP
317 where
318 verbosityP = option
319 ( long "verbosity"
320 <> metavar "LEVEL"
321 <> help ("Set verbosity level\n"
322 ++ "Possible values are " ++ show priorities)
323 )
324
325 verboseP = flag defaultPriority INFO
326 ( long "verbose"
327 <> short 'v'
328 <> help "Verbose mode"
329 )
330
331 quietP = flag defaultPriority CRITICAL
332 ( long "quiet"
333 <> short 'q'
334 <> help "Silent mode"
335 )
336
337
338globalOpts :: Parser GlobalOpts
339globalOpts = GlobalOpts <$> verbosityOpts
340
341data Options = Options
342 { cmdOpts :: Command
343 , globOpts :: GlobalOpts
344 } deriving Show
345
346options :: Parser Options
347options = Options <$> commandOpts <*> globalOpts
348
349versioner :: String -> Version -> Parser (a -> a)
350versioner prog ver = nullOption $ mconcat
351 [ long "version"
352 , help "Show program version and exit"
353 , value id
354 , metavar ""
355 , hidden
356 , reader $ const $ undefined -- Left $ ErrorMsg versionStr
357 ]
358 where
359 versionStr = prog ++ " version " ++ showVersion ver
360
361parserInfo :: ParserInfo Options
362parserInfo = info parser modifier
363 where
364 parser = helper <*> versioner "mktorrent" version <*> options
365 modifier = header synopsis <> progDesc description <> fullDesc
366 synopsis = "Torrent management utility"
367 description = "" -- TODO
368
369{-----------------------------------------------------------------------
370-- Dispatch
371-----------------------------------------------------------------------}
372
373run :: Command -> IO Bool
374run (Amend opts) = amend opts
375--run (Check opts) = checkTorrent opts
376--run (Create opts) = createTorrent opts
377run (Magnet opts) = magnet opts
378run (Show opts) = putTorrent opts
379
380prepare :: GlobalOpts -> IO ()
381prepare GlobalOpts {..} = do
382 updateGlobalLogger rootLoggerName (setLevel verbosity)
383
384main :: IO ()
385main = do
386 Options {..} <- execParser parserInfo
387 prepare globOpts
388 success <- run cmdOpts
389 if success then exitSuccess else exitFailure \ No newline at end of file