{- Copyright 2008 by Ken Takusagawa This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . -} module Main where{ import Control.Exception; import List; import qualified Data.Map as Map; import Maybe; import Char; import qualified Data.ByteString as B; import Data.IORef; import System.IO; import Data.Word; import OpenSSL.Digest; main :: IO(()); main = go2; n_chunk :: Int -> [](a) -> []([](a)); n_chunk n l = (case l of { ([])-> []; (_)-> ((:) (take_enough n l) (n_chunk n (drop n l))) }); take_enough :: Int -> [](a) -> [](a); take_enough n l = (case (compare 0 n) of { (EQ)-> []; (LT)-> (case l of { ((:)(head) (tail))-> ((:) head (take_enough (pred n) (tail ))) }) }); apply_first :: (a -> b) -> (a, c) -> (b, c); apply_first fn x = ((fn (fst x)), (snd x)); tail_assert :: (Eq (a)) => a -> [](a) -> [](a); tail_assert x l = (assert ((==) x (head l)) (tail l)); type SS = (String, String); data Btype = BString(String) | BInteger(Integer) | BList([](Btype)) | BDict(BMap) deriving (Show); type BMap = Map.Map(String)(Btype); parse_bstring :: String -> (Btype, String); parse_bstring s = (let { num_rest :: SS; num_rest = (span isDigit s); num :: Int; num = (read (fst num_rest)); rest :: String; rest = (tail_assert (head ":") (snd num_rest)); rsplit :: SS; rsplit = (splitAt num rest) } in ((apply_first BString)(rsplit))); not_e :: Char -> Bool; not_e c = ((/=) c 'e'); parse_binteger :: String -> (Integer, String); parse_binteger in_s = (let { s :: String; s = (tail_assert (head "i") in_s); num_rest :: SS; num_rest = (span not_e s) } in ((read(fst(num_rest))), ((tail_assert (head "e"))(snd(num_rest))))); parse_blist :: String -> ([](Btype), String); parse_blist s = (case (not_e(head(s))) of { (False)-> ([], (tail_assert (head "e") s)); (_)-> (let { this :: (Btype, String); this = (parse_btype s); rest :: ([](Btype), String); rest = (parse_blist (snd this)) } in (((:) (fst this) (fst rest)), (snd rest))) }); parse_btype :: String -> (Btype, String); parse_btype s = (case (head s) of { ('i')-> ((apply_first BInteger)(parse_binteger(s))); ('l')-> ((apply_first BList)(parse_blist(tail(s)))); ('d')-> ((apply_first BDict)(parse_bdict(s))); (c)-> (assert (isDigit c) (parse_bstring(s))) }); mktuple1 :: [](Btype) -> (String, Btype); mktuple1 l = (case l of { [(BString(s)), (y)]-> (s, y) }); mktuples :: [](Btype) -> []((String, Btype)); mktuples l = ((map mktuple1)((n_chunk 2)(l))); parse_bdict :: String -> (BMap, String); parse_bdict in_s = (let { listparse :: ([](Btype), String); listparse = (parse_blist((tail_assert (head "d"))(in_s))) } in (((Map.fromList )(mktuples(fst(listparse)))), (snd(listparse)))); get :: String -> BMap -> Btype; get s m = (fromJust((Map.lookup s)(m))); get_integer :: Btype -> Int; get_integer b = (case b of { (BInteger(i))-> (fromInteger i) }); get_string :: Btype -> String; get_string b = (case b of { (BString(s))-> s }); get_bmap :: Btype -> BMap; get_bmap b = (case b of { (BDict(d))-> d }); get_blist :: Btype -> [](Btype); get_blist b = (case b of { (BList(l))-> l }); sha_hash_size :: Int; sha_hash_size = 20; check_file_state :: IORef(Maybe(Handle)) -> IORef([](File_record)) -> IORef(File_length) -> IORef([](Filename)) -> IO(Bool); check_file_state p_handle p_files_list p_file_bytes_remaining p_buffer_files = (do{ handle :: Maybe(Handle) <- (readIORef p_handle); (case handle of { (Just(_))-> (return True); (Nothing)-> (do{ files_list :: [](File_record) <- (readIORef p_files_list); (case files_list of { ([])-> (return False); ((:)(h) (t))-> (do{ ((open_file h) >>= ((writeIORef p_handle) . Just)); (writeIORef p_files_list t); (writeIORef p_file_bytes_remaining (snd h)); (modifyIORef p_buffer_files (let { m :: [](Filename) -> [](Filename); m old = ((:) (fst h) old) } in m)); (return True); }) }); }) }); }); open_file :: File_record -> IO(Handle); open_file fr = (do{ (print fr); (openBinaryFile (extract_Filename (fst fr)) ReadMode); }); newtype File_length = File_length(Int) deriving (Eq, Show, Ord, Num); type File_record = (Filename, File_length); newtype Filename = Filename(String) deriving (Show); read_in :: IORef(File_length) -> IORef(File_length) -> IORef(Maybe(Handle)) -> IORef(B.ByteString) -> IO(()); read_in p_buffer_bytes_remaining p_file_bytes_remaining p_handle p_buffer = (do{ buffer_bytes_remaining :: File_length <- (readIORef p_buffer_bytes_remaining); file_bytes_remaining :: File_length <- (readIORef p_file_bytes_remaining); (let { bytes_to_read :: File_length; bytes_to_read = (min buffer_bytes_remaining file_bytes_remaining); subtract_off :: File_length -> File_length; subtract_off old = ((-) old bytes_to_read) } in (do{ (modifyIORef p_buffer_bytes_remaining subtract_off); (modifyIORef p_file_bytes_remaining subtract_off); (read_more_data bytes_to_read p_handle p_buffer); })); }); process_buffer_state :: File_length -> IORef(File_length) -> IORef([](Hash)) -> IORef(B.ByteString) -> IORef([](Filename)) -> IORef(File_length) -> IO(()); process_buffer_state piece_length p_buffer_bytes_remaining p_hashes p_buffer p_buffer_files p_file_bytes_remaining = (do{ hash :: Hash <- (pop_ioref p_hashes); buffer :: B.ByteString <- (readIORef p_buffer); hash_result :: Hash <- (do_hash buffer); buffer_files :: [](Filename) <- (readIORef (p_buffer_files )); file_bytes_remaining :: File_length <- (readIORef p_file_bytes_remaining); (case ((==) hash hash_result) of { (True)-> (return ()); (False)-> (do{ (putStrLn ("Bad hash for" ++ (show(reverse(buffer_files))))); }) }); (writeIORef p_buffer (B.empty )); (writeIORef p_buffer_bytes_remaining piece_length); (case ((==) file_bytes_remaining (File_length 0)) of { (True)-> (writeIORef p_buffer_files []); (False)-> (writeIORef p_buffer_files [(head buffer_files)]) }); }); if_zero :: IORef(File_length) -> IO(()) -> IO(()); if_zero p_x action = ((readIORef p_x) >>= (\lambda_case_var ->case lambda_case_var of { (File_length(0))-> action; (_)-> (return ()) })); process_file_state :: IORef(File_length) -> IORef(Maybe(Handle)) -> IO(()); process_file_state p_file_bytes_remaining p_handle = (if_zero p_file_bytes_remaining (writeIORef p_handle Nothing)); newtype Hash = Hash([](Word8)) deriving (Eq); pop_ioref :: forall a . IORef([](a)) -> IO(a); pop_ioref p = (do{ x :: [](a) <- (readIORef p); (writeIORef p (tail x)); (return (head x)); }); read_more_data :: File_length -> IORef(Maybe(Handle)) -> IORef(B.ByteString) -> IO(()); read_more_data bytes_to_read p_handle p_buffer = (do{ handle :: Maybe(Handle) <- (readIORef p_handle); more_data :: B.ByteString <- (B.hGet (fromJust handle) (extract_File_length bytes_to_read)); old_data :: B.ByteString <- (readIORef p_buffer); (writeIORef p_buffer (B.concat [old_data, more_data])); }); extract_File_length :: File_length -> Int; extract_File_length f = (case f of { (File_length(i))-> i }); extract_Filename :: Filename -> FilePath; extract_Filename fn = (case fn of { (Filename(x))-> x }); do_digest :: [](Word8) -> IO([](Word8)); do_digest l = (digest SHA1 l); do_hash :: B.ByteString -> IO(Hash); do_hash buffer = ((do_digest(B.unpack(buffer))) >>= (return . Hash)); drive_state :: File_length -> IORef(Maybe(Handle)) -> IORef([](File_record)) -> IORef([](Filename)) -> IORef(File_length) -> IORef(File_length) -> IORef(B.ByteString) -> IORef([](Hash)) -> IO(()); drive_state piece_length p_handle p_files_list p_buffer_files p_buffer_bytes_remaining p_file_bytes_remaining p_buffer p_hashes = (do{ continue :: Bool <- (check_file_state p_handle p_files_list p_file_bytes_remaining p_buffer_files); (case continue of { (False)-> (process_buffer_state piece_length p_buffer_bytes_remaining p_hashes p_buffer p_buffer_files p_file_bytes_remaining); (True)-> (do{ (read_in p_buffer_bytes_remaining p_file_bytes_remaining p_handle p_buffer); (if_zero p_buffer_bytes_remaining (process_buffer_state piece_length p_buffer_bytes_remaining p_hashes p_buffer p_buffer_files p_file_bytes_remaining)); (process_file_state p_file_bytes_remaining p_handle); (drive_state piece_length p_handle p_files_list p_buffer_files p_buffer_bytes_remaining p_file_bytes_remaining p_buffer p_hashes); }) }); }); init_and_drive :: File_length -> [](File_record) -> [](Hash) -> IO(()); init_and_drive piece_length files_list hashes = (do{ (hSetBuffering stdout LineBuffering); p_handle :: IORef(Maybe(Handle)) <- (newIORef Nothing); p_files_list :: IORef([](File_record)) <- (newIORef files_list); p_buffer_files :: IORef([](Filename)) <- (newIORef []); p_buffer_bytes_remaining :: IORef(File_length) <- (newIORef piece_length); p_file_bytes_remaining :: IORef(File_length) <- (newIORef undefined); p_buffer :: IORef(B.ByteString) <- (newIORef B.empty); p_hashes :: IORef([](Hash)) <- (newIORef hashes); (drive_state piece_length p_handle p_files_list p_buffer_files p_buffer_bytes_remaining p_file_bytes_remaining p_buffer p_hashes); }); get_hashes :: BMap -> [](Hash); get_hashes bt1 = ((map Hash)((n_chunk sha_hash_size)((map (fromIntegral . ord))(get_string((get "pieces")(bt1)))))); go2 :: IO(()); go2 = (do{ fi :: String <- getContents; (let { b :: BMap; b = (get_bmap((get "info")(fst(parse_bdict(fi))))); all_files :: [](File_record); all_files = (get_all_files b) } in (do{ (putStrLn(show_list(all_files))); (init_and_drive (get_piece_length b) all_files (get_hashes b)); })); }); get_piece_length :: BMap -> File_length; get_piece_length bt1 = (File_length(get_integer((get "piece length")(bt1)))); get_file_record :: Btype -> File_record; get_file_record bt1 = (let { bfile :: BMap; bfile = (get_bmap(bt1)) } in ((make_file_name(get_blist((get "path")(bfile)))), (File_length(get_integer((get "length")(bfile)))))); make_file_name :: [](Btype) -> Filename; make_file_name path = (Filename(concat((intersperse path_separator)((map get_string)(path))))); path_separator :: String; path_separator = "/"; get_all_files :: BMap -> [](File_record); get_all_files bt1 = ((map get_file_record)(get_blist((get "files")(bt1)))); show_list :: (Show (a)) => [](a) -> String; show_list l = (unlines (map show l)) }