diff options
Diffstat (limited to 'src/Data/Torrent/URN.hs')
-rw-r--r-- | src/Data/Torrent/URN.hs | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/src/Data/Torrent/URN.hs b/src/Data/Torrent/URN.hs new file mode 100644 index 00000000..cb3aef02 --- /dev/null +++ b/src/Data/Torrent/URN.hs | |||
@@ -0,0 +1,133 @@ | |||
1 | -- | | ||
2 | -- Copyright : (c) Sam Truzjan 2013 | ||
3 | -- License : BSD3 | ||
4 | -- Maintainer : pxqr.sta@gmail.com | ||
5 | -- Stability : experimental | ||
6 | -- Portability : portable | ||
7 | -- | ||
8 | -- URN is pesistent location-independent identifier for | ||
9 | -- resources. In particular, URNs are used represent torrent names | ||
10 | -- as a part of magnet link, see 'Data.Torrent.Magnet.Magnet' for | ||
11 | -- more info. | ||
12 | -- | ||
13 | {-# LANGUAGE MultiParamTypeClasses #-} | ||
14 | {-# LANGUAGE DeriveDataTypeable #-} | ||
15 | module Data.Torrent.URN | ||
16 | ( -- * Namespaces | ||
17 | NamespaceId | ||
18 | , btih | ||
19 | |||
20 | -- * URN | ||
21 | , URN (..) | ||
22 | |||
23 | -- ** Construction | ||
24 | , infohashURN | ||
25 | |||
26 | -- ** Conversion | ||
27 | , parseURN | ||
28 | , renderURN | ||
29 | ) where | ||
30 | |||
31 | import Control.Applicative | ||
32 | import Data.Convertible | ||
33 | import Data.Default | ||
34 | import Data.List as L | ||
35 | import Data.String | ||
36 | import Data.Text as T | ||
37 | import Data.Typeable | ||
38 | import Network.HTTP.Types.QueryLike | ||
39 | import Text.PrettyPrint | ||
40 | import Text.PrettyPrint.Class | ||
41 | |||
42 | import Data.Torrent.InfoHash | ||
43 | |||
44 | |||
45 | -- | Namespace identifier determines the syntactic interpretation of | ||
46 | -- namespace-specific string. | ||
47 | type NamespaceId = [Text] | ||
48 | |||
49 | -- | BitTorrent Info Hash (hence the name) namespace | ||
50 | -- identifier. Namespace-specific string /should/ be a base16\/base32 | ||
51 | -- encoded SHA1 hash of the corresponding torrent /info/ dictionary. | ||
52 | -- | ||
53 | btih :: NamespaceId | ||
54 | btih = ["btih"] | ||
55 | |||
56 | -- | Uniform Resource Name - location-independent, resource | ||
57 | -- identifier. | ||
58 | -- | ||
59 | data URN = URN | ||
60 | { urnNamespace :: NamespaceId -- ^ a namespace identifier; | ||
61 | , urnString :: Text -- ^ a corresponding | ||
62 | -- namespace-specific string. | ||
63 | } deriving (Eq, Ord, Typeable) | ||
64 | |||
65 | {----------------------------------------------------------------------- | ||
66 | -- Infohash convertion | ||
67 | -----------------------------------------------------------------------} | ||
68 | |||
69 | instance Convertible URN InfoHash where | ||
70 | safeConvert u @ URN {..} | ||
71 | | urnNamespace /= btih = convError "invalid namespace" u | ||
72 | | otherwise = safeConvert urnString | ||
73 | |||
74 | -- | Make resource name for torrent with corresponding | ||
75 | -- infohash. Infohash is base16 (hex) encoded). | ||
76 | -- | ||
77 | infohashURN :: InfoHash -> URN | ||
78 | infohashURN = URN btih . longHex | ||
79 | |||
80 | -- | Meaningless placeholder value. | ||
81 | instance Default URN where | ||
82 | def = infohashURN def | ||
83 | |||
84 | {----------------------------------------------------------------------- | ||
85 | -- Rendering | ||
86 | -----------------------------------------------------------------------} | ||
87 | |||
88 | -- | Render URN to its text representation. | ||
89 | renderURN :: URN -> Text | ||
90 | renderURN URN {..} | ||
91 | = T.intercalate ":" $ "urn" : urnNamespace ++ [urnString] | ||
92 | |||
93 | instance Pretty URN where | ||
94 | pretty = text . T.unpack . renderURN | ||
95 | |||
96 | instance Show URN where | ||
97 | showsPrec n = showsPrec n . T.unpack . renderURN | ||
98 | |||
99 | instance QueryValueLike URN where | ||
100 | toQueryValue = toQueryValue . renderURN | ||
101 | {-# INLINE toQueryValue #-} | ||
102 | |||
103 | {----------------------------------------------------------------------- | ||
104 | -- Parsing | ||
105 | -----------------------------------------------------------------------} | ||
106 | |||
107 | unsnoc :: [a] -> Maybe ([a], a) | ||
108 | unsnoc [] = Nothing | ||
109 | unsnoc xs = Just (L.init xs, L.last xs) | ||
110 | |||
111 | instance Convertible Text URN where | ||
112 | safeConvert t = case T.split (== ':') t of | ||
113 | uriScheme : body | ||
114 | | T.toLower uriScheme == "urn" -> | ||
115 | case unsnoc body of | ||
116 | Just (namespace, val) -> pure URN | ||
117 | { urnNamespace = namespace | ||
118 | , urnString = val | ||
119 | } | ||
120 | Nothing -> convError "missing URN string" body | ||
121 | | otherwise -> convError "invalid URN scheme" uriScheme | ||
122 | [] -> convError "missing URN scheme" t | ||
123 | |||
124 | instance IsString URN where | ||
125 | fromString = either (error . prettyConvertError) id | ||
126 | . safeConvert . T.pack | ||
127 | |||
128 | -- | Try to parse an URN from its text representation. | ||
129 | -- | ||
130 | -- Use 'safeConvert' for detailed error messages. | ||
131 | -- | ||
132 | parseURN :: Text -> Maybe URN | ||
133 | parseURN = either (const Nothing) pure . safeConvert | ||