summaryrefslogtreecommitdiff
path: root/src/System/Torrent/Storage.hs
blob: c00f770a64b1d34a1fd68dd3a26c9214be2cabad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
-- |
--   Copyright   :  (c) Sam T. 2013
--   License     :  MIT
--   Maintainer  :  pxqr.sta@gmail.com
--   Stability   :  experimental
--   Portability :  non-portable
--
--   This module implements mapping from single continious block space
--   to file storage. Storage can be used in two modes:
--
--     * As in memory storage - in this case we don't touch filesystem.
--
--     * As ordinary mmaped file storage - when we need to store
--       data in the filesystem.
--
--
{-# LANGUAGE DoAndIfThenElse #-}
{-# LANGUAGE NamedFieldPuns  #-}
{-# LANGUAGE RecordWildCards #-}
module Cobit.Storage
       ( Storage

         -- * Construction
       , mapStorage, unmapStorage, withStorage

         -- * Modification
       , getBlk, putBlk
       ) where

import Control.Applicative
import Control.Exception
import Control.Monad
import Data.ByteString as B
import qualified Data.ByteString.Lazy as Lazy
import Data.List as L
import System.FilePath
import System.Directory

import Data.Torrent
import Network.BitTorrent
import System.IO.MMap.Fixed


data Storage = Storage {
    session :: SwarmSession

    -- | Used to map linear block addresses to disjoint mallocated/mmaped adresses.
  , stPayload :: Fixed

    -- | Used to find out block offsets.
  , stPieceSize :: Int

    -- | Used to verify pieces and set finally mark piece as verified.
  , stHashes :: ByteString
  }

-- TODO doc args
mapStorage :: Int -> Maybe FilePath -> FilePath -> ContentInfo -> IO Storage
mapStorage blkSize statusPath contentPath ci = do
  let content_paths = contentLayout contentPath ci
  mapM_ mkDir (L.map fst content_paths)
  Storage <$> coalesceFiles content_paths
          <*> getAllocator statusPath (pieceCount ci) (blockCount blkSize ci)
          <*> pure (ciPieceLength ci)
          <*> pure (ciPieces ci)
  where
    getAllocator (Just path) = error "getAllocator"
    getAllocator Nothing     = error "getAllocator"

    mkDir path = do
      let dirPath = fst (splitFileName path)
      exist <- doesDirectoryExist dirPath
      unless exist $ do
        createDirectoryIfMissing True dirPath

unmapStorage :: Storage -> IO ()
unmapStorage st = error "unmapStorage"


withStorage :: Int -> Maybe FilePath -> FilePath -> ContentInfo
            -> (Storage -> IO a)
            -> IO a
withStorage blkSize statusPath contentPath t =
  bracket (mapStorage blkSize statusPath contentPath t) unmapStorage

isAvailable :: BlockIx -> Storage -> IO Bool
isAvailable ix Storage {..} = error "isAvailable"


putBlk :: Block -> Storage -> IO ()
putBlk blk @ Block {..}  st @ Storage {..} = do
  available <- isAvailable (blockIx blk) st
  unless available $
    writeBytes (blkInterval stPieceSize blk) (Lazy.fromChunks [blkData]) stPayload

-- TODO
getBlk :: BlockIx -> Storage -> IO (Maybe Block)
getBlk ix @ BlockIx {..}  st @ Storage {..} = do
    available <- isAvailable ix st
    if available
      then Just <$> do
        bs <- readBytes (ixInterval stPieceSize ix) stPayload
        return $ Block ixPiece ixOffset (Lazy.toStrict bs)
      else return Nothing

ixInterval :: Int -> BlockIx -> FixedInterval
ixInterval pieceSize BlockIx {..} =
  interval (ixPiece * pieceSize + ixOffset) ixLength

blkInterval :: Int -> Block -> FixedInterval
blkInterval pieceSize Block {..} =
  interval (blkPiece * pieceSize + blkOffset) (B.length blkData)