rtorrent-state-0.1.0.1: src/Data/Rtorrent/StateFile.hs
{-# LANGUAGE UnicodeSyntax #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- |
-- Module : Data.Rtorrent.StateFile
-- Copyright : (c) Mateusz Kowalczyk 2014
-- License : GPL-3
--
-- Maintainer : fuuzetsu@fuuzetsu.co.uk
-- Stability : experimental
-- Portability : portable
--
-- Exposes few convenient functions to work with 'StateFile's. For the
-- types and the instances actually used during parsing see
-- "Data.Rtorrent.StateFile.Types".
--
-- A few handy lenses for accessing and manipulating a 'StateFile' are
-- provided but they are not meant to be comprehensive: for most
-- scenarios, you can just use the generated lenses directly.
--
-- An example use-case of this module is stopping all torrents without
-- relying on rtorrent itself. This might be useful if your rtorrent
-- has troubles starting up. Here it is as easy as:
--
-- @'overFilesIn' "rtorrent/session/dir" 'stopTorrent'@
module Data.Rtorrent.StateFile where
import Control.Applicative ((<$>))
import Control.Exception (catch, IOException)
import Control.Lens ((.~), set, view)
import Control.Monad ((>=>))
import Data.BEncode (decode, encode, Result)
import Data.ByteString (readFile, writeFile, length)
import qualified Data.ByteString.Lazy as L (toStrict, ByteString)
import Data.ByteString.UTF8 (fromString, toString, ByteString)
import Data.List (isSuffixOf)
import Data.Map (Map, mapEither, fromList)
import Data.Rtorrent.StateFile.Types
import Prelude hiding (readFile, writeFile, length)
import System.Directory (getDirectoryContents)
import System.FilePath ((</>))
-- | Takes a directory, 'StateFile' modification function and does an
-- ‘in-place’ modifications to all .rtorrent files it can find and
-- parse in that directory. Returns the list of all file paths and any
-- potential errors that with each.
--
-- See 'getRtorrentFiles' for the type of exception this function can throw.
overFilesIn ∷ FilePath → StateMod → IO [(FilePath, Maybe String)]
overFilesIn fp f =
getRtorrentFiles fp >>= mapM (\x → (x,) <$> withRtorrentState x f)
-- | Gets a list of all @.rtorrent@ files in the specified directory.
--
-- This function uses 'getDirectoryContents' which can throw various
-- IOExceptions. It's up to the user to catch these if they wish to do
-- so.
getRtorrentFiles ∷ FilePath → IO [FilePath]
getRtorrentFiles t =
map (t </>) . filter (".rtorrent" `isSuffixOf`) <$> getDirectoryContents t
-- | Attempts to read in the file at specified file path, change it
-- with the user-supplied function and saved the file with changes.
--
-- In case the parsing fails, the result will be @Just errMsg@.
withRtorrentState ∷ FilePath → StateMod → IO (Maybe String)
withRtorrentState fp f = parseFile fp >>= \case
Left s → return $ Just s
Right x → writeFile fp (encodeState' (f x)) >> return Nothing
-- | Only keeps properly parsing results. Useful with 'parseFiles'.
keepValid ∷ Map FilePath (Result StateFile) → Map FilePath StateFile
keepValid = snd . mapEither id
-- | Similar to 'keepValid' but instead only keeps results that failed
-- to parse and the reason.
keepInvalid ∷ Map FilePath (Result StateFile) → Map FilePath String
keepInvalid = fst . mapEither id
-- | Given a directory path, produces a 'Map' of file paths from
-- individual files to their parsing results.
--
-- This function uses 'getRtorrentFiles' which can throw an
-- IOException in case there is a problem reading the directory.
parseFiles ∷ FilePath → IO (Map FilePath (Result StateFile))
parseFiles t = getRtorrentFiles t >>= mapM parseFile' >>= return . fromList
-- | Like 'parseFile'' but throws away the 'FilePath': useful if we
-- only play with single files.
parseFile ∷ FilePath → IO (Result StateFile)
parseFile = parseFile' >=> return . snd
-- | Parses a file and returns a pair of of its path and the parsing
-- result. The path is useful if we're processing whole directories.
parseFile' ∷ FilePath → IO (FilePath, Result StateFile)
parseFile' f = readFileE >>= return . (f,) . \case
Left (e ∷ IOException) → Left $ show e
Right c → length c `seq` decodeState c
where
readFileE ∷ IO (Either IOException ByteString)
readFileE = (Right <$> readFile f) `catch` (return . Left)
-- | Sets the torrent to started state.
startTorrent ∷ StateMod
startTorrent = state .~ 1
-- | Sets the torrent to stopped stated.
stopTorrent ∷ StateMod
stopTorrent = state .~ 0
-- | Changes the file the torrent is tied to.
setTiedFile ∷ FilePath → StateMod
setTiedFile = set tiedToFile . BS8 . fromString
-- | Gets a path of the loaded file.
getLoadedFile ∷ StateFile → FilePath
getLoadedFile = toString . _bs8 . view loadedFile
-- | Wrapper for 'decode' which works for 'StateFile's.
decodeState ∷ ByteString → Result StateFile
decodeState = decode
-- | Wrapper for 'encode' which works for 'StateFile's.
--
-- See 'encodeState'' for strict 'ByteString' version.
encodeState ∷ StateFile → L.ByteString
encodeState = encode
-- | Encodes a 'StateFile' to a strict 'ByteString'.
encodeState' ∷ StateFile → ByteString
encodeState' = L.toStrict . encode