diff options
Diffstat (limited to 'dht/bittorrent/src/System/Torrent/FileMap.hs')
-rw-r--r-- | dht/bittorrent/src/System/Torrent/FileMap.hs | 163 |
1 files changed, 163 insertions, 0 deletions
diff --git a/dht/bittorrent/src/System/Torrent/FileMap.hs b/dht/bittorrent/src/System/Torrent/FileMap.hs new file mode 100644 index 00000000..38c475e8 --- /dev/null +++ b/dht/bittorrent/src/System/Torrent/FileMap.hs | |||
@@ -0,0 +1,163 @@ | |||
1 | {-# LANGUAGE RecordWildCards #-} | ||
2 | {-# LANGUAGE ViewPatterns #-} | ||
3 | {-# OPTIONS -fno-warn-orphans #-} | ||
4 | module System.Torrent.FileMap | ||
5 | ( FileMap | ||
6 | |||
7 | -- * Construction | ||
8 | , Mode (..) | ||
9 | , def | ||
10 | , mmapFiles | ||
11 | , unmapFiles | ||
12 | |||
13 | -- * Query | ||
14 | , System.Torrent.FileMap.size | ||
15 | |||
16 | -- * Modification | ||
17 | , readBytes | ||
18 | , writeBytes | ||
19 | , unsafeReadBytes | ||
20 | |||
21 | -- * Unsafe conversions | ||
22 | , fromLazyByteString | ||
23 | , toLazyByteString | ||
24 | ) where | ||
25 | |||
26 | import Control.Applicative | ||
27 | import Control.Monad as L | ||
28 | import Data.ByteString as BS | ||
29 | import Data.ByteString.Internal as BS | ||
30 | import Data.ByteString.Lazy as BL | ||
31 | import Data.ByteString.Lazy.Internal as BL | ||
32 | import Data.Default | ||
33 | import Data.Vector as V -- TODO use unboxed vector | ||
34 | import Foreign | ||
35 | import System.IO.MMap | ||
36 | |||
37 | import Data.Torrent | ||
38 | |||
39 | |||
40 | data FileEntry = FileEntry | ||
41 | { filePosition :: {-# UNPACK #-} !FileOffset | ||
42 | , fileBytes :: {-# UNPACK #-} !BS.ByteString -- XXX: mutable buffer (see 'writeBytes'). | ||
43 | } deriving (Show, Eq) | ||
44 | |||
45 | type FileMap = Vector FileEntry | ||
46 | |||
47 | instance Default Mode where | ||
48 | def = ReadWriteEx | ||
49 | |||
50 | mmapFiles :: Mode -> FileLayout FileSize -> IO FileMap | ||
51 | mmapFiles mode layout = V.fromList <$> L.mapM mkEntry (accumPositions layout) | ||
52 | where | ||
53 | mkEntry (path, (pos, expectedSize)) = do | ||
54 | let esize = fromIntegral expectedSize -- FIXME does this safe? | ||
55 | (fptr, moff, msize) <- mmapFileForeignPtr path mode $ Just (0, esize) | ||
56 | if msize /= esize | ||
57 | then error "mmapFiles" -- TODO unmap mapped files on exception | ||
58 | else return $ FileEntry pos (PS fptr moff msize) | ||
59 | |||
60 | unmapFiles :: FileMap -> IO () | ||
61 | unmapFiles = V.mapM_ unmapEntry | ||
62 | where | ||
63 | unmapEntry (FileEntry _ (PS fptr _ _)) = finalizeForeignPtr fptr | ||
64 | |||
65 | -- Unsafe: FileMap 'writeBytes' will modify supplied bytestrings in place. | ||
66 | fromLazyByteString :: BL.ByteString -> FileMap | ||
67 | fromLazyByteString lbs = V.unfoldr f (0, lbs) | ||
68 | where | ||
69 | f (_, Empty ) = Nothing | ||
70 | f (pos, Chunk x xs) = Just (FileEntry pos x, ((pos + chunkSize), xs)) | ||
71 | where chunkSize = fromIntegral $ BS.length x | ||
72 | |||
73 | -- | /O(n)/. | ||
74 | -- | ||
75 | -- Unsafe: mutable buffers are returned without copy. | ||
76 | toLazyByteString :: FileMap -> BL.ByteString | ||
77 | toLazyByteString = V.foldr f Empty | ||
78 | where | ||
79 | f FileEntry {..} bs = Chunk fileBytes bs | ||
80 | |||
81 | -- | /O(1)/. | ||
82 | size :: FileMap -> FileOffset | ||
83 | size m | ||
84 | | V.null m = 0 | ||
85 | | FileEntry {..} <- V.unsafeLast m | ||
86 | = filePosition + fromIntegral (BS.length fileBytes) | ||
87 | |||
88 | -- | Find the file number for a particular byte offset within a torrent. | ||
89 | bsearch :: FileOffset -> FileMap -> Maybe Int | ||
90 | bsearch x m | ||
91 | | V.null m = Nothing | ||
92 | | otherwise = branch (V.length m `div` 2) | ||
93 | where | ||
94 | branch c @ ((m !) -> FileEntry {..}) | ||
95 | | x < filePosition = bsearch x (V.take c m) | ||
96 | | x >= filePosition + fileSize = do | ||
97 | ix <- bsearch x (V.drop (succ c) m) | ||
98 | return $ succ c + ix | ||
99 | | otherwise = Just c | ||
100 | where | ||
101 | fileSize = fromIntegral (BS.length fileBytes) | ||
102 | |||
103 | -- | /O(log n)/. | ||
104 | drop :: FileOffset -> FileMap -> (FileSize, FileMap) | ||
105 | drop off m | ||
106 | | Just ix <- bsearch off m | ||
107 | , FileEntry {..} <- m ! ix = (off - filePosition, V.drop ix m) | ||
108 | | otherwise = (0 , V.empty) | ||
109 | |||
110 | -- | /O(log n)/. | ||
111 | take :: FileSize -> FileMap -> (FileMap, FileSize) | ||
112 | take len m | ||
113 | | len >= s = (m , 0) | ||
114 | | Just ix <- bsearch (pred len) m = let m' = V.take (succ ix) m | ||
115 | in (m', System.Torrent.FileMap.size m' - len) | ||
116 | | otherwise = (V.empty , 0) | ||
117 | where | ||
118 | s = System.Torrent.FileMap.size m | ||
119 | |||
120 | -- | /O(log n + m)/. Do not use this function with 'unmapFiles'. | ||
121 | -- | ||
122 | -- The returned bytestring points directly into an area memory mapped from a | ||
123 | -- file. | ||
124 | unsafeReadBytes :: FileOffset -> FileSize -> FileMap -> BL.ByteString | ||
125 | unsafeReadBytes off s m | ||
126 | | (l , m') <- System.Torrent.FileMap.drop off m | ||
127 | , (m'', _ ) <- System.Torrent.FileMap.take (off + s) m' | ||
128 | = BL.take (fromIntegral s) $ BL.drop (fromIntegral l) $ toLazyByteString m'' | ||
129 | |||
130 | -- The returned bytestring is copied and safe to use after the file is | ||
131 | -- unmapped. | ||
132 | readBytes :: FileOffset -> FileSize -> FileMap -> IO BL.ByteString | ||
133 | readBytes off s m = do | ||
134 | let bs_copy = BL.copy $ unsafeReadBytes off s m | ||
135 | forceLBS bs_copy | ||
136 | return bs_copy | ||
137 | where | ||
138 | forceLBS Empty = return () | ||
139 | forceLBS (Chunk _ x) = forceLBS x | ||
140 | |||
141 | -- UNSAFE: Uses the first byte string as a pointer to mutable data and writes | ||
142 | -- the contents of the second bytestring there. | ||
143 | bscpy :: BL.ByteString -> BL.ByteString -> IO () | ||
144 | bscpy (PS _ _ 0 `Chunk` dest_rest) src = bscpy dest_rest src | ||
145 | bscpy dest (PS _ _ 0 `Chunk` src_rest) = bscpy dest src_rest | ||
146 | bscpy (PS dest_fptr dest_off dest_size `Chunk` dest_rest) | ||
147 | (PS src_fptr src_off src_size `Chunk` src_rest) | ||
148 | = do let csize = min dest_size src_size | ||
149 | withForeignPtr dest_fptr $ \dest_ptr -> | ||
150 | withForeignPtr src_fptr $ \src_ptr -> | ||
151 | memcpy (dest_ptr `advancePtr` dest_off) | ||
152 | (src_ptr `advancePtr` src_off) | ||
153 | (fromIntegral csize) -- TODO memmove? | ||
154 | bscpy (PS dest_fptr (dest_off + csize) (dest_size - csize) `Chunk` dest_rest) | ||
155 | (PS src_fptr (src_off + csize) (src_size - csize) `Chunk` src_rest) | ||
156 | bscpy _ _ = return () | ||
157 | |||
158 | -- UNSAFE: Mutates bytestring contents within the provided FileMap. | ||
159 | writeBytes :: FileOffset -> BL.ByteString -> FileMap -> IO () | ||
160 | writeBytes off lbs m = bscpy dest src | ||
161 | where | ||
162 | src = BL.take (fromIntegral (BL.length dest)) lbs | ||
163 | dest = unsafeReadBytes off (fromIntegral (BL.length lbs)) m | ||