Hello community,

here is the log from the commit of package ghc-irc-dcc for openSUSE:Factory 
checked in at 2017-04-17 10:25:32
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/ghc-irc-dcc (Old)
 and      /work/SRC/openSUSE:Factory/.ghc-irc-dcc.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "ghc-irc-dcc"

Mon Apr 17 10:25:32 2017 rev:2 rq:485137 version:2.0.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/ghc-irc-dcc/ghc-irc-dcc.changes  2017-03-08 
00:58:43.652135636 +0100
+++ /work/SRC/openSUSE:Factory/.ghc-irc-dcc.new/ghc-irc-dcc.changes     
2017-04-17 10:25:45.162960246 +0200
@@ -1,0 +2,10 @@
+Mon Mar 27 12:41:56 UTC 2017 - psim...@suse.com
+
+- Update to version 2.0.1 with cabal2obs.
+
+-------------------------------------------------------------------
+Thu Sep 15 06:44:50 UTC 2016 - psim...@suse.com
+
+- Update to version 2.0.0 revision 0 with cabal2obs.
+
+-------------------------------------------------------------------

Old:
----
  1.cabal
  irc-dcc-1.2.1.tar.gz

New:
----
  irc-dcc-2.0.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ ghc-irc-dcc.spec ++++++
--- /var/tmp/diff_new_pack.N4BkGQ/_old  2017-04-17 10:25:48.006557540 +0200
+++ /var/tmp/diff_new_pack.N4BkGQ/_new  2017-04-17 10:25:48.010556973 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package ghc-irc-dcc
 #
-# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany.
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -19,26 +19,25 @@
 %global pkg_name irc-dcc
 %bcond_with tests
 Name:           ghc-%{pkg_name}
-Version:        1.2.1
+Version:        2.0.1
 Release:        0
 Summary:        A DCC message parsing and helper library for IRC clients
 License:        MIT
-Group:          System/Libraries
+Group:          Development/Languages/Other
 Url:            https://hackage.haskell.org/package/%{pkg_name}
 Source0:        
https://hackage.haskell.org/package/%{pkg_name}-%{version}/%{pkg_name}-%{version}.tar.gz
-Source1:        
https://hackage.haskell.org/package/%{pkg_name}-%{version}/revision/1.cabal
 BuildRequires:  ghc-Cabal-devel
-# Begin cabal-rpm deps:
 BuildRequires:  ghc-attoparsec-devel
 BuildRequires:  ghc-binary-devel
 BuildRequires:  ghc-bytestring-devel
-BuildRequires:  ghc-errors-devel
 BuildRequires:  ghc-io-streams-devel
 BuildRequires:  ghc-iproute-devel
 BuildRequires:  ghc-irc-ctcp-devel
+BuildRequires:  ghc-mtl-devel
 BuildRequires:  ghc-network-devel
 BuildRequires:  ghc-path-devel
 BuildRequires:  ghc-rpm-macros
+BuildRequires:  ghc-safe-exceptions-devel
 BuildRequires:  ghc-transformers-devel
 BuildRequires:  ghc-utf8-string-devel
 BuildRoot:      %{_tmppath}/%{name}-%{version}-build
@@ -46,8 +45,8 @@
 BuildRequires:  ghc-hspec-attoparsec-devel
 BuildRequires:  ghc-tasty-devel
 BuildRequires:  ghc-tasty-hspec-devel
+BuildRequires:  ghc-tasty-quickcheck-devel
 %endif
-# End cabal-rpm deps
 
 %description
 DCC (Direct Client-to-Client) is an IRC sub-protocol for establishing and
@@ -68,22 +67,15 @@
 
 %prep
 %setup -q -n %{pkg_name}-%{version}
-cp -p %{SOURCE1} %{pkg_name}.cabal
-
 
 %build
 %ghc_lib_build
 
-
 %install
 %ghc_lib_install
 
-
 %check
-%if %{with tests}
-%{cabal} test
-%endif
-
+%cabal_test
 
 %post devel
 %ghc_pkg_recache
@@ -97,5 +89,6 @@
 
 %files devel -f %{name}-devel.files
 %defattr(-,root,root,-)
+%doc CHANGELOG.md
 
 %changelog

++++++ irc-dcc-1.2.1.tar.gz -> irc-dcc-2.0.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/irc-dcc-1.2.1/CHANGELOG.md 
new/irc-dcc-2.0.1/CHANGELOG.md
--- old/irc-dcc-1.2.1/CHANGELOG.md      1970-01-01 01:00:00.000000000 +0100
+++ new/irc-dcc-2.0.1/CHANGELOG.md      2017-03-19 06:29:39.000000000 +0100
@@ -0,0 +1,3 @@
+# 2.0.1
+* Remove unused `errors` package from dependencies
+* Relax upper bounds for package `binary`
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/irc-dcc-1.2.1/irc-dcc.cabal 
new/irc-dcc-2.0.1/irc-dcc.cabal
--- old/irc-dcc-1.2.1/irc-dcc.cabal     2016-05-27 19:43:11.000000000 +0200
+++ new/irc-dcc-2.0.1/irc-dcc.cabal     2017-03-19 06:27:38.000000000 +0100
@@ -1,65 +1,67 @@
-name:                 irc-dcc
-version:              1.2.1
-synopsis:             A DCC message parsing and helper library for IRC clients
-description:          DCC (Direct Client-to-Client) is an IRC sub-protocol for
-                      establishing and maintaining direct connections to
-                      exchange messages and files.
-                      .
-                      See <http://www.irchelp.org/irchelp/rfc/ctcpspec.html> 
for
-                      more details.
-license:              MIT
-license-file:         LICENSE
-homepage:             https://github.com/JanGe/irc-dcc
-bug-reports:          https://github.com/JanGe/irc-dcc/issues
-author:               Jan Gerlinger
-maintainer:           g...@jangerlinger.de
-category:             Network
--- copyright:
-build-type:           Simple
-cabal-version:        >= 1.10
+name:                irc-dcc
+version:             2.0.1
+synopsis:            A DCC message parsing and helper library for IRC clients
+description:         DCC (Direct Client-to-Client) is an IRC sub-protocol for
+                     establishing and maintaining direct connections to
+                     exchange messages and files.
+                     .
+                     See <http://www.irchelp.org/irchelp/rfc/ctcpspec.html> for
+                     more details.
+license:             MIT
+license-file:        LICENSE
+extra-source-files:  CHANGELOG.md
+homepage:            https://github.com/JanGe/irc-dcc
+bug-reports:         https://github.com/JanGe/irc-dcc/issues
+author:              Jan Gerlinger
+maintainer:          g...@jangerlinger.de
+category:            Network
+copyright:           2016 Jan Gerlinger
+build-type:          Simple
+cabal-version:       >= 1.10
 
 library
-  hs-source-dirs:       src
-  exposed-modules:      Network.IRC.DCC
-                      , Network.IRC.DCC.FileTransfer
-  other-modules:        Network.IRC.DCC.Internal
-                      , Network.Socket.ByteString.Extended
+  hs-source-dirs:      src
+  exposed-modules:     Network.IRC.DCC
+                     , Network.IRC.DCC.Internal
+                     , Network.IRC.DCC.Client.FileTransfer
+  other-modules:       Network.Socket.ByteString.Extended
+                     , System.IO.Streams.Lifted
   -- other-extensions:
-  build-depends:        base         >= 4.7      && < 5
-                      , attoparsec   >= 0.13.0.1 && < 0.14
-                      , binary       >= 0.7.5.0
-                      , bytestring   >= 0.10.6.0 && < 0.11
-                      , errors       >= 2.1.2    && < 2.2
-                      , io-streams   >= 1.3.5.0  && < 1.4
-                      , iproute      >= 1.7.0    && < 1.8
-                      , irc-ctcp     >= 0.1.3.0  && < 0.2
-                      , network      >= 2.6.2.1  && < 2.7
-                      , path         >= 0.5.7    && < 0.6
-                      , transformers >= 0.4.2.0
-                      , utf8-string  >= 1.0.1.1  && < 1.1
-  default-language:     Haskell2010
-  ghc-options:          -Wall -fno-warn-unused-do-bind
+  build-depends:       base            >= 4.8      && < 5
+                     , attoparsec      >= 0.13.0.1 && < 0.14
+                     , binary          >= 0.7.5.0  && < 0.10
+                     , bytestring      >= 0.10.6.0 && < 0.11
+                     , io-streams      >= 1.3.5.0  && < 1.4
+                     , iproute         >= 1.7.0    && < 1.8
+                     , irc-ctcp        >= 0.1.3.0  && < 0.2
+                     , mtl             >= 2.2.1    && < 2.3
+                     , network         >= 2.6.2.1  && < 2.7
+                     , path            >= 0.5.7    && < 0.6
+                     , safe-exceptions >= 0.1.2.0  && < 0.2
+                     , transformers    >= 0.4.2.0  && < 0.6
+                     , utf8-string     >= 1.0.1.1  && < 1.1
+  default-language:    Haskell2010
+  ghc-options:         -Wall -fno-warn-unused-do-bind
 
-test-suite tests
-  hs-source-dirs:       tests
-                      , src
-  main-is:              Main.hs
-  other-modules:        Network.IRC.DCCTest
-                      , Network.IRC.DCC.Internal
-  type:                 exitcode-stdio-1.0
-  build-depends:        base             >= 4.7      && < 5
-                      , tasty            >= 0.11.0.2 && < 0.12
-                      , tasty-hspec      >= 1.1.2    && < 1.2
-                      , hspec-attoparsec >= 0.1.0.2  && < 0.2
-                      , attoparsec       >= 0.13.0.1 && < 0.14
-                      , binary           >= 0.7.5.0
-                      , bytestring       >= 0.10.6.0 && < 0.11
-                      , iproute          >= 1.7.0    && < 1.8
-                      , irc-ctcp         >= 0.1.3.0  && < 0.2
-                      , network          >= 2.6.2.1  && < 2.7
-                      , path             >= 0.5.7    && < 0.6
-                      , utf8-string      >= 1.0.1.1  && < 1.1
-  default-language:     Haskell2010
+test-suite irc-dcc-test
+  type:                exitcode-stdio-1.0
+  hs-source-dirs:      test
+  main-is:             Spec.hs
+  other-modules:       Network.IRC.DCCTest
+  build-depends:       base             >= 4.8      && < 5
+                     , bytestring       >= 0.10.6.0 && < 0.11
+                     , hspec-attoparsec >= 0.1.0.2  && < 0.2
+                     , iproute          >= 1.7.0    && < 1.8
+                     , irc-dcc
+                     , network          >= 2.6.2.1  && < 2.7
+                     , path             >= 0.5.7    && < 0.6
+                     , utf8-string      >= 1.0.1.1  && < 1.1
+                     , tasty            >= 0.11.0.2 && < 0.12
+                     , tasty-hspec      >= 1.1.2    && < 1.2
+                     , tasty-quickcheck >= 0.8.4    && < 0.9
+  ghc-options:         -Wall -fno-warn-orphans
+                       -threaded -rtsopts -with-rtsopts=-N
+  default-language:    Haskell2010
 
 source-repository head
   type:     git
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/irc-dcc-1.2.1/src/Network/IRC/DCC/Client/FileTransfer.hs 
new/irc-dcc-2.0.1/src/Network/IRC/DCC/Client/FileTransfer.hs
--- old/irc-dcc-1.2.1/src/Network/IRC/DCC/Client/FileTransfer.hs        
1970-01-01 01:00:00.000000000 +0100
+++ new/irc-dcc-2.0.1/src/Network/IRC/DCC/Client/FileTransfer.hs        
2017-03-19 06:24:03.000000000 +0100
@@ -0,0 +1,136 @@
+{-# LANGUAGE RecordWildCards #-}
+
+{-| Functions for receiving files.
+
+    Each chunk is acknowledged by sending the total sum of bytes received for a
+    file. See the
+    <http://www.irchelp.org/irchelp/rfc/ctcpspec.html CTCP specification>.
+-}
+module Network.IRC.DCC.Client.FileTransfer (
+    -- * Start/resume DCC file transfer
+      acceptFile
+    , resumeFile
+    -- * Custom DCC file transfer
+    , FileTransfer(..)
+    , ConnectionType(..)
+    , TransferType(..)
+    , transfer
+    ) where
+
+import           Network.IRC.DCC
+
+import           Control.Exception.Safe
+import           Control.Monad                      (unless)
+import           Control.Monad.IO.Class             (MonadIO, liftIO)
+import           Control.Monad.Trans.Class          (lift)
+import           Control.Monad.Trans.Reader         (ReaderT, ask)
+import           Data.ByteString.Char8              (ByteString, length, null)
+import           Network.Socket.ByteString.Extended (ConnectionType (..),
+                                                     PortNumber, Socket, close,
+                                                     connect, recv, sendAll,
+                                                     toNetworkByteOrder)
+import qualified Path                               as P (File, Path, Rel,
+                                                          fromRelFile)
+import           Prelude                            hiding (length, null)
+import           System.IO                          (BufferMode (NoBuffering), 
IOMode (AppendMode, WriteMode))
+import           System.IO.Streams                  (OutputStream, write)
+import           System.IO.Streams.Lifted           (withFileAsOutputExt)
+
+data TransferType = FromStart
+                  | ResumeFrom !FileOffset
+
+-- | A description of a file transfer.
+--   You can specify a callback that will be called with the number of bytes
+--   transfered for each chunk.
+data FileTransfer m = Download { _fileName       :: !(P.Path P.Rel P.File)
+                               , _connectionType :: !(ConnectionType m)
+                               , _transferType   :: !TransferType
+                               , _onChunk        :: FileOffset -> m ()
+                               }
+
+-- | Accept a DCC file offer
+acceptFile :: DccSend
+           -> (PortNumber -> IO ())
+           -- ^ Callback when socket is ready
+           -> (FileOffset -> IO ())
+           -- ^ Callback when a chunk of data was transfered
+           -> ReaderT (Maybe PortNumber) IO ()
+acceptFile = download FromStart
+
+-- | Accept a DCC file offer for a partially downloaded file
+resumeFile :: DccSend
+           -> DccAccept
+           -> (PortNumber -> IO ())
+           -- ^ Callback when socket is ready
+           -> (FileOffset -> IO ())
+           -- ^ Callback when a chunk of data was transferred
+           -> ReaderT (Maybe PortNumber) IO ()
+resumeFile offer accept
+    | accept `matchesSend` offer =
+          download (ResumeFrom pos) offer
+    | otherwise = fail "You mixed the DCC and Reverse DCC workflow when 
calling 'resumeFile'."
+  where
+    pos = acceptedPosition accept
+
+download :: TransferType
+         -> DccSend
+         -> (PortNumber -> IO ())
+         -> (FileOffset -> IO ())
+         -> ReaderT (Maybe PortNumber) IO ()
+download tt (Send path ip port _) onListen onChunk =
+    lift $ transfer Download { _fileName       = fromPath path
+                             , _connectionType = Active ip port (onListen port)
+                             , _transferType   = tt
+                             , _onChunk        = onChunk
+                             }
+download tt (SendReverseServer path ip _ _) onListen onChunk = do
+    port <- ask
+    lift $ transfer Download { _fileName       = fromPath path
+                             , _connectionType = Passive ip port onListen
+                             , _transferType   = tt
+                             , _onChunk        = onChunk
+                             }
+
+transfer :: (MonadMask m, MonadIO m) => FileTransfer m -> m ()
+transfer Download {..} =
+    bracket (connect _connectionType)
+            (liftIO . close)
+            (streamToFile _fileName _transferType _onChunk)
+
+streamToFile :: (MonadMask m, MonadIO m)
+             => P.Path P.Rel P.File
+             -> TransferType
+             -> (FileOffset -> m ())
+             -- ^ Callback when a chunk of data was transfered
+             -> Socket
+             -> m ()
+streamToFile name tt onChunk =
+    withFileAsOutputExt (P.fromRelFile name) (mode tt) NoBuffering .
+        stream (pos tt) onChunk
+  where
+    mode FromStart      = WriteMode
+    mode (ResumeFrom _) = AppendMode
+    pos FromStart       = 0
+    pos (ResumeFrom p)  = p
+
+stream :: (MonadMask m, MonadIO m)
+       => FileOffset
+       -> (FileOffset -> m ())
+       -> Socket
+       -> OutputStream ByteString
+       -> m ()
+stream pos onChunk sock h = do
+    buf <- liftIO $ recv sock (4096 * 1024)
+    unless (null buf) $ do
+        let len = fromIntegral $ length buf
+        onChunk len
+        let pos' = pos + len
+        sendPosition sock pos'
+        liftIO $ Just buf `write` h
+        stream pos' onChunk sock h
+
+sendPosition :: (MonadMask m, MonadIO m)
+             => Socket
+             -> FileOffset
+             -> m ()
+sendPosition sock = liftIO . sendAll sock . toNetworkByteOrder
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/irc-dcc-1.2.1/src/Network/IRC/DCC/FileTransfer.hs 
new/irc-dcc-2.0.1/src/Network/IRC/DCC/FileTransfer.hs
--- old/irc-dcc-1.2.1/src/Network/IRC/DCC/FileTransfer.hs       2016-04-13 
20:54:00.000000000 +0200
+++ new/irc-dcc-2.0.1/src/Network/IRC/DCC/FileTransfer.hs       1970-01-01 
01:00:00.000000000 +0100
@@ -1,85 +0,0 @@
-{-| Functions for receiving files.
-
-    Each chunk is acknowledged by sending the total sum of bytes received for a
-    file. See the
-    <http://www.irchelp.org/irchelp/rfc/ctcpspec.html CTCP specification>.
--}
-module Network.IRC.DCC.FileTransfer
-  ( acceptFile
-  , resumeFile
-  ) where
-
-import           Network.IRC.DCC.Internal
-
-import           Control.Error
-import           Control.Monad                      (unless)
-import           Control.Monad.Trans.Class          (lift)
-import           Control.Monad.Trans.Reader         (ReaderT, ask)
-import           Data.ByteString.Char8              (ByteString, length, null)
-import           Network.Socket.ByteString.Extended
-import           Path                               (File, Path, Rel,
-                                                     fromRelFile)
-import           Prelude                            hiding (length, null)
-import           System.IO                          (BufferMode (NoBuffering), 
IOMode (AppendMode, WriteMode))
-import           System.IO.Streams                  (OutputStream,
-                                                     withFileAsOutputExt, 
write)
-
--- | Accept a DCC file offer
-acceptFile :: Integral a
-           => OfferFile
-           -> (PortNumber -> ExceptT String IO ())
-           -- ^ Callback when socket is ready
-           -> (a -> IO ())
-           -- ^ Callback when a chunk of data was transfered
-           -> ReaderT (Maybe PortNumber) (ExceptT String IO) ()
-acceptFile (OfferFile tt f) =
-    download (fileName f) WriteMode 0 tt
-
--- | Accept a DCC file offer for a partially downloaded file
-resumeFile :: Integral a
-           => AcceptResumeFile
-           -> (PortNumber -> ExceptT String IO ())
-           -- ^ Callback when socket is ready
-           -> (a -> IO ())
-           -- ^ Callback when a chunk of data was transfered
-           -> ReaderT (Maybe PortNumber) (ExceptT String IO) ()
-resumeFile (AcceptResumeFile tt f pos) =
-    download (fileName f) AppendMode (fromIntegral pos) tt
-
-download :: Integral a
-         => Path Rel File
-         -> IOMode
-         -> a
-         -> TransferType
-         -> (PortNumber -> ExceptT String IO ())
-           -- ^ Callback when socket is ready
-         -> (a -> IO ())
-           -- ^ Callback when a chunk of data was transfered
-         -> ReaderT (Maybe PortNumber) (ExceptT String IO) ()
-download fn mode pos tt onListen onChunk = do
-    localPort <- ask
-    lift $
-        withSocket tt localPort onListen $
-            withFileAsOutputExt (fromRelFile fn) mode NoBuffering .
-                stream pos onChunk
-  where withSocket (Active i p) _ = withActiveSocket (Sink i p)
-        withSocket (Passive i _) p = withPassiveSocket (Source i p)
-
-stream :: Integral a
-       => a
-       -> (a -> IO ())
-       -> Socket
-       -> OutputStream ByteString
-       -> IO ()
-stream pos onChunk sock h =
-  do buf <- recv sock (4096 * 1024)
-     unless (null buf) $
-            do let l = fromIntegral (length buf)
-               onChunk l
-               let pos' = pos + l
-               sendPosition sock pos'
-               Just buf `write` h
-               stream pos' onChunk sock h
-
-sendPosition :: Integral a => Socket -> a -> IO ()
-sendPosition sock = sendAll sock . toNetworkByteOrder
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/irc-dcc-1.2.1/src/Network/IRC/DCC/Internal.hs 
new/irc-dcc-2.0.1/src/Network/IRC/DCC/Internal.hs
--- old/irc-dcc-1.2.1/src/Network/IRC/DCC/Internal.hs   2016-04-01 
14:24:22.000000000 +0200
+++ new/irc-dcc-2.0.1/src/Network/IRC/DCC/Internal.hs   2016-08-21 
18:21:49.000000000 +0200
@@ -3,115 +3,265 @@
 
 module Network.IRC.DCC.Internal where
 
-import           Control.Applicative
-import           Control.Monad                    (when)
-import           Data.Attoparsec.ByteString.Char8
-import           Data.Binary                      (byteSwap32)
-import           Data.ByteString.Char8            (ByteString, pack)
-import qualified Data.ByteString.UTF8             as UTF8 (toString)
-import           Data.IP                          (IPv4, fromHostAddress,
-                                                   toHostAddress)
-import           Data.Monoid                      ((<>))
-import           Data.Word                        (Word64)
-import           Network.IRC.CTCP                 (CTCPByteString, decodeCTCP,
-                                                   encodeCTCP)
-import           Network.Socket                   (PortNumber)
-import           Path                             (File, Path, Rel, filename,
-                                                   fromRelFile, parseAbsFile,
-                                                   parseRelFile)
-
--- | Type of DCC service offered
-data Service
-  -- | Offer chat session
-  = Messaging OpenChat
-  -- | Offer file transfer
-  | FileTransfer OfferFile
-  deriving (Eq, Show)
+import           Control.Applicative                ((<|>))
+import           Control.Monad                      (when)
+import           Data.Attoparsec.ByteString.Char8   (Parser, choice, decimal,
+                                                     endOfInput, parseOnly,
+                                                     space, takeByteString,
+                                                     takeWhile1)
+import           Data.Binary                        (byteSwap32)
+import           Data.ByteString.Char8              (ByteString, pack, unwords)
+import qualified Data.ByteString.UTF8               as UTF8 (fromString,
+                                                             toString)
+import           Data.IP                            (IPv4, fromHostAddress,
+                                                     toHostAddress)
+import           Data.Monoid                        ((<>))
+import           Data.Word                          (Word64)
+import           Network.IRC.CTCP                   (CTCPByteString, 
decodeCTCP,
+                                                     encodeCTCP)
+import           Network.Socket.ByteString.Extended (PortNumber)
+import qualified Path                               as P (Abs, File, Path, Rel,
+                                                          filename, 
fromAbsFile,
+                                                          fromRelFile,
+                                                          parseAbsFile,
+                                                          parseRelFile)
+import           Prelude                            hiding (abs, unwords)
+
+-- | CTCP commands that can be parsed and encoded
+class CtcpCommand a where
+    toCtcp :: a -> CTCPByteString
+    fromCtcp :: CTCPByteString -> Either String a
+
+parseCtcp :: Parser a -> CTCPByteString -> Either String a
+parseCtcp p = parseOnly (p <* endOfInput) . decodeCTCP
 
--- | Type of DCC chat to open
-data OpenChat
+-- | Offer DCC chat session
+data DccChat
   {-| Text messages exchange
 
       > DCC CHAT chat <ip> <port>
   -}
-  = Chat IPv4 PortNumber
+  = Chat !IPv4 !PortNumber
   {-| Drawing commands exchange
 
       > DCC CHAT wboard <ip> <port>
   -}
-  | Whiteboard IPv4 PortNumber
+  | ChatWhiteboard !IPv4 !PortNumber
   deriving (Eq, Show)
 
+instance CtcpCommand DccChat where
+    toCtcp (Chat ip port) = encodeCTCP $ unwords
+        [ "DCC CHAT chat"
+        , socketToBS (ip, port)
+        ]
+    toCtcp (ChatWhiteboard ip port) = encodeCTCP $ unwords
+        [ "DCC CHAT wboard"
+        , socketToBS (ip, port)
+        ]
+
+    fromCtcp = parseCtcp $
+      Chat
+          <$> ("DCC CHAT chat" *> spaced ipBigEndian)
+          <*> spaced tcpPort
+      <|> ChatWhiteboard
+          <$> ("DCC CHAT wboard" *> spaced ipBigEndian)
+          <*> spaced tcpPort
+
 -- | Signal intent to close DCC chat connection
-data CloseChat
+data DccClose
   -- | > DCC CLOSE
-  = CloseChat
+  = Close
   deriving (Eq, Show)
 
--- | DCC file transfer instructions
-data OfferFile
-  {-| DCC:
-
-      > DCC SEND <fileName> <ip> <port> (<fileSize>)
+instance CtcpCommand DccClose where
+    toCtcp Close = encodeCTCP "DCC CLOSE"
+    fromCtcp = parseCtcp $ Close <$ "DCC CLOSE"
+
+-- | Offer DCC file transfer
+data DccSend
+  {-| As part of the standard DCC protocol, sent by the server
 
-      Reverse DCC:
+      > DCC SEND <path> <ip> <port> (<fileSize>)
+  -}
+  = Send !Path !IPv4 !PortNumber !(Maybe FileOffset)
+  {-| As part of the Reverse DCC protocol, sent by the server
 
-      > DCC SEND <fileName> <ip> 0 <fileSize> <token>
+      > DCC SEND <path> <ip> 0 <fileSize> <token>
   -}
-  = OfferFile TransferType FileMetadata
+  | SendReverseServer !Path !IPv4 !FileOffset !Token
   deriving (Eq, Show)
 
--- | Signal intent to resume DCC file transfer at specific position
-data TryResumeFile
-  {-| DCC:
+instance CtcpCommand DccSend where
+    toCtcp (Send name ip port size) = encodeCTCP $ unwords
+        [ "DCC SEND"
+        , pathToBS name
+        , ipToBigEndianBS ip
+        , tcpPortToBS port ]
+        <> maybe "" ((" " <>) . fileOffsetToBS) size
+    toCtcp (SendReverseServer name ip size t) = encodeCTCP $ unwords
+        [ "DCC SEND"
+        , pathToBS name
+        , ipToBigEndianBS ip
+        , "0"
+        , fileOffsetToBS size
+        , tokenToBS t
+        ]
+
+    fromCtcp = parseCtcp $
+        Send
+            <$> ("DCC SEND" *> spaced path)
+            <*> spaced ipBigEndian
+            <*> spaced tcpPort
+            <*> (Just <$> spaced fileOffset
+                <|> return Nothing)
+        <|> SendReverseServer
+            <$> ("DCC SEND" *> spaced path)
+            <*> spaced ipBigEndian
+            <*> (spaced "0" *> spaced fileOffset)
+            <*> spaced token
 
-      > DCC RESUME <fileName> <port> <position>
+-- | Signal intent to resume DCC file transfer at specific position
+data DccResume
+  {-| As part of the standard DCC protocol, sent by the client
 
-      Reverse DCC:
+      > DCC RESUME <path> <port> <position>
+  -}
+  = Resume !Path !PortNumber !FileOffset
+  {-| As part of the Reverse DCC protocol, sent by the client
 
-      > DCC RESUME <fileName> 0 <position> <token>
-    -}
-  = TryResumeFile TransferType FileMetadata FileOffset
+      > DCC RESUME <path> 0 <position> <token>
+  -}
+  | ResumeReverse !Path !FileOffset !Token
   deriving (Eq, Show)
 
--- | Signal acceptance to resume DCC file transfer at specific position
-data AcceptResumeFile
-  {-| DCC:
+instance CtcpCommand DccResume where
+    toCtcp (Resume name port pos) = encodeCTCP $ unwords
+        [ "DCC RESUME"
+        , pathToBS name
+        , tcpPortToBS port
+        , fileOffsetToBS pos
+        ]
+    toCtcp (ResumeReverse name pos t) = encodeCTCP $ unwords
+        [ "DCC RESUME"
+        , pathToBS name
+        , "0"
+        , fileOffsetToBS pos
+        , tokenToBS t
+        ]
+
+    fromCtcp = parseCtcp $
+        Resume
+            <$> ("DCC RESUME" *> spaced path)
+            <*> spaced tcpPort
+            <*> spaced fileOffset
+        <|> ResumeReverse
+            <$> ("DCC RESUME" *> spaced path)
+            <*> (spaced "0" *> spaced fileOffset)
+            <*> spaced token
 
-      > DCC ACCEPT <fileName> <port> <position>
+-- | Signal acceptance to resume DCC file transfer at specific position
+data DccAccept
+  {-| As part of the standard DCC protocol, sent by the server
 
-      Reverse DCC:
+      > DCC ACCEPT <path> <port> <position>
+  -}
+  = Accept !Path !PortNumber !FileOffset
+  {-| As part of the Reverse DCC protocol, sent by the server
 
-      > DCC ACCEPT <fileName> 0 <position> <token>
+      > DCC ACCEPT <path> 0 <position> <token>
   -}
-  = AcceptResumeFile TransferType FileMetadata FileOffset
+  | AcceptReverse !Path !FileOffset !Token
   deriving (Eq, Show)
 
--- | Signal readiness to accept a connection (only Reverse DCC)
-data OfferFileSink
-  {-| Reverse DCC:
+acceptedPosition :: DccAccept -> FileOffset
+acceptedPosition (Accept _ _ pos)        = pos
+acceptedPosition (AcceptReverse _ pos _) = pos
+
+instance CtcpCommand DccAccept where
+    toCtcp (Accept name port pos) = encodeCTCP $ unwords
+        [ "DCC ACCEPT"
+        , pathToBS name
+        , tcpPortToBS port
+        , fileOffsetToBS pos
+        ]
+    toCtcp (AcceptReverse name pos t) = encodeCTCP $ unwords
+        [ "DCC ACCEPT"
+        , pathToBS name
+        , "0"
+        , fileOffsetToBS pos
+        , tokenToBS t
+        ]
+
+    fromCtcp = parseCtcp $
+        Accept
+            <$> ("DCC ACCEPT" *> spaced path)
+            <*> spaced tcpPort
+            <*> spaced fileOffset
+        <|> AcceptReverse
+            <$> ("DCC ACCEPT" *> spaced path)
+            <*> (spaced "0" *> spaced fileOffset)
+            <*> spaced token
+
+-- | Tell the server to start a DCC file transfer and where it should send the 
data to.
+data DccSendReverseClient
+  {-| As part of the Reverse DCC protocol, sent by the client
 
-      > DCC SEND <fileName> <ip> <port> <fileSize> <token>
+      > DCC SEND <path> <ip> <port> <fileSize> <token>
   -}
-  = OfferFileSink Token FileMetadata IPv4 PortNumber
+  = SendReverseClient !Path !IPv4 !PortNumber !FileOffset !Token
   deriving (Eq, Show)
 
--- | Type of a DCC file transfer connection
-data TransferType
-  -- | Connection where the owner of the file offers a socket to connect to
-  = Active IPv4 PortNumber
-  -- | Connection where the recipient of the file offers a socket to connect to
-  | Passive IPv4 Token
+instance CtcpCommand DccSendReverseClient where
+  toCtcp (SendReverseClient name ip port size t) = encodeCTCP $ unwords
+      [ "DCC SEND"
+      , pathToBS name
+      , ipToBigEndianBS ip
+      , tcpPortToBS port
+      , fileOffsetToBS size
+      , tokenToBS t
+      ]
+
+  fromCtcp = parseCtcp $
+      SendReverseClient
+          <$> ("DCC SEND" *> spaced path)
+          <*> spaced ipBigEndian
+          <*> spaced tcpPort
+          <*> spaced fileOffset
+          <*> spaced token
+
+data PathType
+  = Simple
+  -- ^ A file path without spaces
+  | Quoted
+  -- ^ A file path that can include spaces and will be quoted when serialized
   deriving (Eq, Show)
 
--- | Properties of a file
-data FileMetadata = FileMetadata { fileName :: Path Rel File
-                                 , fileSize :: Maybe FileOffset }
+data Path = Rel PathType (P.Path P.Rel P.File)
+          | Abs PathType (P.Path P.Abs P.File)
   deriving (Eq, Show)
 
--- | An identifier for knowing which negotiation a request belongs to
-newtype Token = Token ByteString
-  deriving (Eq, Show)
+fromPath :: Path -> P.Path P.Rel P.File
+fromPath (Rel _ name) = name
+fromPath (Abs _ name) = P.filename name
+
+path :: Parser Path
+path = choice [ quoted >>= parseRelOrAbs Quoted
+              , simple >>= parseRelOrAbs Simple ]
+  where
+    quoted = UTF8.toString <$> ("\"" *> takeWhile1 (/= '"') <* "\"")
+    simple = UTF8.toString <$> takeWhile1 (/= ' ')
+    parseRelOrAbs ty n =
+            maybe (fail "Could not parse file name.") return
+                ( Rel ty <$> P.parseRelFile n
+              <|> Abs ty <$> P.parseAbsFile n )
+
+pathToBS :: Path -> ByteString
+pathToBS (Rel ty name) = wrap ty . UTF8.fromString . P.fromRelFile $ name
+pathToBS (Abs ty name) = wrap ty . UTF8.fromString . P.fromAbsFile $ name
+
+wrap :: PathType -> ByteString -> ByteString
+wrap Simple p = p
+wrap Quoted p = "\"" <> p <> "\""
 
 newtype FileOffset = FileOffset { toWord :: Word64 }
   deriving (Eq, Ord, Num, Integral, Enum, Real, Bounded)
@@ -119,260 +269,53 @@
 instance Show FileOffset where
   show = show . toWord
 
--- | Class for types that can be sent as CTCP commands
-class CtcpCommand a where
-    encodeCtcp :: a -> CTCPByteString
+fileOffset :: Parser FileOffset
+fileOffset = FileOffset <$> decimal
 
-instance CtcpCommand ByteString where
-    encodeCtcp = encodeCTCP
+fileOffsetToBS :: FileOffset -> ByteString
+fileOffsetToBS = pack . show . toWord
 
-instance CtcpCommand OpenChat where
-    encodeCtcp = encodeCtcp . encodeOpenChat
+-- | An identifier for knowing which negotiation a request belongs to
+newtype Token = Token ByteString
+  deriving (Eq, Show)
 
-instance CtcpCommand CloseChat where
-    encodeCtcp = encodeCtcp . encodeChatClose
-
-instance CtcpCommand OfferFile where
-    encodeCtcp = encodeCtcp . encodeOfferFile
-
-instance CtcpCommand TryResumeFile where
-    encodeCtcp = encodeCtcp . encodeTryResumeFile
-
-instance CtcpCommand AcceptResumeFile where
-    encodeCtcp = encodeCtcp . encodeAcceptResume
-
-instance CtcpCommand OfferFileSink where
-    encodeCtcp = encodeCtcp . encodeOfferFileSink
-
-runParser :: Parser a -> CTCPByteString -> Either String a
-runParser p = parseOnly p . decodeCTCP
-
-parseService :: Parser Service
-parseService = Messaging <$> parseOpenChat
-           <|> FileTransfer <$> parseOfferFile
-
-encodeService :: Service -> ByteString
-encodeService (Messaging m) = encodeOpenChat m
-encodeService (FileTransfer o) = encodeOfferFile o
-
-parseOpenChat :: Parser OpenChat
-parseOpenChat = choice [ do "DCC CHAT chat "
-                            (i, p) <- parseSocket
-                            endOfInput
-                            return $ Chat i p
-                       , do "DCC CHAT wboard "
-                            (i, p) <- parseSocket
-                            endOfInput
-                            return $ Whiteboard i p
-                       ]
-
-encodeOpenChat :: OpenChat -> ByteString
-encodeOpenChat (Chat i p) = "DCC CHAT chat " <> encodeSocket (i, p)
-encodeOpenChat (Whiteboard i p) = "DCC CHAT wboard " <> encodeSocket (i, p)
-
-parseCloseChat :: Parser CloseChat
-parseCloseChat = do "DCC CLOSE"
-                    endOfInput
-                    return CloseChat
-
-encodeChatClose :: CloseChat -> ByteString
-encodeChatClose _ = "DCC CLOSE"
-
-parseOfferFile :: Parser OfferFile
-parseOfferFile =
-    do "DCC SEND "
-       fn <- parseFileName
-       space
-       i <- parseIpBigEndian
-       space
-       choice [ do p <- parseTcpPort
-                   fs <- Just <$> (space *> parseFileOffset)
-                     <|> return Nothing
-                   endOfInput
-                   return (OfferFile
-                              (Active i p)
-                              (FileMetadata fn fs))
-              , do "0"
-                   space
-                   fs <- parseFileOffset
-                   space
-                   t <- parseToken
-                   endOfInput
-                   return (OfferFile
-                              (Passive i t)
-                              (FileMetadata fn (Just fs)))
-              ]
-
-encodeOfferFile :: OfferFile -> ByteString
-encodeOfferFile (OfferFile (Active i p) (FileMetadata fn fs)) =
-    "DCC SEND "
- <> encodeFileName fn
- <> " " <> encodeIpBigEndian i
- <> " " <> encodeTcpPort p
- <> appendSpacedIfJust (encodeFileOffset <$> fs)
-encodeOfferFile (OfferFile (Passive i t) (FileMetadata fn fs)) =
-    "DCC SEND "
- <> encodeFileName fn
- <> " " <> encodeIpBigEndian i
- <> " 0"
- <> appendSpacedIfJust (encodeFileOffset <$> fs)
- <> " " <> encodeToken t
-
-parseTryResumeFile :: OfferFile -> Parser TryResumeFile
-parseTryResumeFile (OfferFile tt f) =
-    do "DCC RESUME"
-       space
-       fn' <- parseFileName
-       when (fn' /= fileName f) $
-         fail "File name for resume didn't match file name in offer."
-       space
-       pos <- case tt of
-                Active _ p -> do
-                    string (encodeTcpPort p)
-                    space
-                    parseFileOffset
-                Passive _ t -> do
-                    "0"
-                    space
-                    pos <- parseFileOffset
-                    space
-                    string (encodeToken t)
-                    return pos
-       endOfInput
-       return (TryResumeFile tt f pos)
-
-
-encodeTryResumeFile :: TryResumeFile -> ByteString
-encodeTryResumeFile (TryResumeFile (Active _ p) (FileMetadata fn _) pos) =
-    "DCC RESUME "
- <> encodeFileName fn
- <> " " <> encodeTcpPort p
- <> " " <> encodeFileOffset pos
-encodeTryResumeFile (TryResumeFile (Passive _ t) (FileMetadata fn _) pos) =
-    "DCC RESUME "
- <> encodeFileName fn
- <> " 0 "
- <> encodeFileOffset pos
- <> " " <> encodeToken t
-
-parseAcceptResumeFile :: TryResumeFile -> Parser AcceptResumeFile
-parseAcceptResumeFile (TryResumeFile tt f _) =
-    do "DCC ACCEPT "
-       fn' <- parseFileName
-       when (fn' /= fileName f) $
-         fail "File name for accepting resume didn't match file name in offer."
-       space
-       ackPos <- case tt of
-                   Active _ p -> do
-                       string (encodeTcpPort p)
-                       space
-                       parseFileOffset
-                   Passive _ t -> do
-                       "0"
-                       space
-                       ackPos <- parseFileOffset
-                       space
-                       string (encodeToken t)
-                       return ackPos
-       endOfInput
-       return (AcceptResumeFile tt f ackPos)
-
-encodeAcceptResume :: AcceptResumeFile -> ByteString
-encodeAcceptResume (AcceptResumeFile (Active _ p) (FileMetadata fn _) pos) =
-    "DCC ACCEPT "
- <> encodeFileName fn
- <> " " <> encodeTcpPort p
- <> " " <> encodeFileOffset pos
-encodeAcceptResume (AcceptResumeFile (Passive _ t) (FileMetadata fn _) pos) =
-    "DCC ACCEPT "
- <> encodeFileName fn
- <> " 0 "
- <> encodeFileOffset pos
- <> " " <> encodeToken t
-
-parseOfferFileSink :: AcceptResumeFile -> Parser (Maybe OfferFileSink)
-parseOfferFileSink (AcceptResumeFile (Passive _ t) f _) =
-    do "DCC SEND "
-       fn <- parseFileName
-       when (fn /= fileName f) $
-         fail "File name for accepting resume didn't match file name in offer."
-       space
-       (i, p) <- parseSocket
-       string (appendSpacedIfJust (encodeFileOffset <$> fileSize f))
-       space
-       string (encodeToken t)
-       endOfInput
-       return (Just (OfferFileSink t f i p))
-parseOfferFileSink _ = return Nothing
-
-encodeOfferFileSink :: OfferFileSink -> ByteString
-encodeOfferFileSink (OfferFileSink t (FileMetadata fn fs) i p) =
-    "DCC SEND "
- <> encodeFileName fn
- <> " " <> encodeSocket (i, p)
- <> appendSpacedIfJust (encodeFileOffset <$> fs)
- <> " " <> encodeToken t
-
-parseSocket :: Parser (IPv4, PortNumber)
-parseSocket =
-    do i <- parseIpBigEndian
-       space
-       p <- parseTcpPort
-       return (i, p)
-
-encodeSocket :: (IPv4, PortNumber) -> ByteString
-encodeSocket (i, p) = encodeIpBigEndian i <> " " <> encodeTcpPort p
-
-parseFileName :: Parser (Path Rel File)
-parseFileName = do name <- "\"" *> takeWhile1 (/= '"') <* "\""
-                       <|> takeWhile1 (/= ' ')
-                   case parseRelOrAbs (UTF8.toString name) of
-                     Nothing -> fail "Could not parse file name."
-                     Just path -> return path
-  where parseRelOrAbs n = parseRelFile n
-                      <|> filename <$> parseAbsFile n
-
-encodeFileName :: Path Rel File -> ByteString
-encodeFileName = pack . fromRelFile
-
-parseIpBigEndian :: Parser IPv4
-parseIpBigEndian = fromBigEndianIp <$> parseBoundedInteger 0 4294967295
-
-encodeIpBigEndian :: IPv4 -> ByteString
-encodeIpBigEndian = pack . show . toBigEndianIp
-
-parseTcpPort :: Parser PortNumber
-parseTcpPort = fromInteger <$> parseBoundedInteger 1 65535
-
-encodeTcpPort :: PortNumber -> ByteString
-encodeTcpPort = pack . show
-
-parseFileOffset :: Parser FileOffset
-parseFileOffset = FileOffset <$> decimal
-
-encodeFileOffset :: FileOffset -> ByteString
-encodeFileOffset = pack . show . toWord
+token :: Parser Token
+token = Token <$> takeByteString
 
-parseToken :: Parser Token
-parseToken = Token <$> takeByteString
+tokenToBS :: Token -> ByteString
+tokenToBS (Token t) = t
 
-encodeToken :: Token -> ByteString
-encodeToken (Token t) = t
+socket :: Parser (IPv4, PortNumber)
+socket = (,) <$> ipBigEndian <* space <*> tcpPort
 
-parseBoundedInteger :: Integer -> Integer -> Parser Integer
-parseBoundedInteger lower upper = do
-    num <- decimal
-    when (num < lower || num > upper) $
-         fail ("Failed to parse " ++ show num ++ ", not in range [" ++
-               show lower ++ ", " ++ show upper ++ "].")
-    return num
+socketToBS :: (IPv4, PortNumber) -> ByteString
+socketToBS (i, p) = ipToBigEndianBS i <> " " <> tcpPortToBS p
 
-appendSpacedIfJust :: Maybe ByteString -> ByteString
-appendSpacedIfJust (Just x) = " " <> x
-appendSpacedIfJust Nothing = mempty
+ipBigEndian :: Parser IPv4
+ipBigEndian = fromBigEndianIp <$> decimalInRange (0, 4294967295)
+
+ipToBigEndianBS :: IPv4 -> ByteString
+ipToBigEndianBS = pack . show . toBigEndianIp
 
 fromBigEndianIp :: Integer -> IPv4
 fromBigEndianIp = fromHostAddress . byteSwap32 . fromIntegral
 
 toBigEndianIp :: IPv4 -> Integer
 toBigEndianIp = fromIntegral . byteSwap32 . toHostAddress
+
+tcpPort :: Parser PortNumber
+tcpPort = fromInteger <$> decimalInRange (1, 65535)
+
+tcpPortToBS :: PortNumber -> ByteString
+tcpPortToBS = pack . show
+
+decimalInRange :: (Integer, Integer) -> Parser Integer
+decimalInRange (lower, upper) = do
+    num <- decimal
+    when (num < lower || num > upper) $
+       fail ( "Failed to parse " ++ show num ++ ", not in range ["
+           ++ show lower ++ ", " ++ show upper ++ "]." )
+    return num
+
+spaced :: Parser a -> Parser a
+spaced = (space *>)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/irc-dcc-1.2.1/src/Network/IRC/DCC.hs 
new/irc-dcc-2.0.1/src/Network/IRC/DCC.hs
--- old/irc-dcc-1.2.1/src/Network/IRC/DCC.hs    2016-03-29 17:48:10.000000000 
+0200
+++ new/irc-dcc-2.0.1/src/Network/IRC/DCC.hs    2016-08-21 18:21:49.000000000 
+0200
@@ -1,40 +1,54 @@
 {-| DCC command parsing and encoding module.
 
-    Example of parsing an offer file command:
+    Use the 'CtcpCommand' type class to convert between 'CTCPByteString's
+    and typed values.
 
-    > runParser parseOfferFile ctcpMessage
+    Try converting a 'CTCPByteString' to a 'DccSend' value:
 
-    Example of encoding an offer file command:
+    > fromCtcp ctcpMessage :: Either String DccSend
 
-    > encodeCtcp offerFile
+    Encoding a 'DccSend' value to a 'CTCPByteString':
+
+    > toCtcp (Send fileName ip port (Just fileSize))
 -}
 module Network.IRC.DCC (
-  -- * Types
-  -- ** DCC service
+  -- * DCC command parsing and encoding
     CtcpCommand(..)
-  , Service(..)
+  -- * DCC command types
   -- ** Messaging commands (DCC CHAT)
-  , OpenChat(..)
-  , CloseChat(..)
+  , DccChat(..)
+  , DccClose(..)
   -- ** File Transfer commands (DCC SEND)
-  , OfferFile(..)
-  , TryResumeFile(..)
-  , AcceptResumeFile(..)
-  , OfferFileSink(..)
-  -- *** Helper Types
-  , TransferType(..)
-  , FileMetadata(..)
-  , Token(..)
+  , DccSend(..)
+  , DccResume(..)
+  , DccAccept(..)
+  , acceptedPosition
+  , DccSendReverseClient(..)
+  -- *** Constructors from other commands
+  , resumeFromSend
+  -- *** Protocol variant checks
+  , matchesSend
+  -- ** Helper Types
+  , Path(..)
+  , fromPath
+  , PathType(..)
   , FileOffset
-  -- * DCC command parsing
-  , runParser
-  , parseService
-  , parseOpenChat
-  , parseCloseChat
-  , parseOfferFile
-  , parseTryResumeFile
-  , parseAcceptResumeFile
-  , parseOfferFileSink
+  , Token(..)
   ) where
 
-import Network.IRC.DCC.Internal
+import           Network.IRC.DCC.Internal
+
+-- | Try resuming a file offer
+resumeFromSend :: DccSend -> FileOffset -> DccResume
+resumeFromSend (Send path' _ port _) pos =
+    Resume path' port pos
+resumeFromSend (SendReverseServer path' _ _ token') pos =
+    ResumeReverse path' pos token'
+
+-- | Check if a 'DccSend' and a 'DccAccept' command are part of the same 
negotiation.
+matchesSend :: DccAccept -> DccSend -> Bool
+matchesSend (Accept pathA portA _) (Send pathS _ portS _) =
+    pathS == pathA && portS == portA
+matchesSend (AcceptReverse pathA _ tokenA) (SendReverseServer pathS _ _ 
tokenS) =
+    pathS == pathA && tokenS == tokenA
+matchesSend _ _ = False
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/irc-dcc-1.2.1/src/Network/Socket/ByteString/Extended.hs 
new/irc-dcc-2.0.1/src/Network/Socket/ByteString/Extended.hs
--- old/irc-dcc-1.2.1/src/Network/Socket/ByteString/Extended.hs 2016-05-10 
15:08:33.000000000 +0200
+++ new/irc-dcc-2.0.1/src/Network/Socket/ByteString/Extended.hs 2016-08-21 
18:21:49.000000000 +0200
@@ -1,77 +1,85 @@
 -- | Common functions simplyfing the use of "Network.Socket.ByteString"
 module Network.Socket.ByteString.Extended
-  ( module Network.Socket
-  , module Network.Socket.ByteString
-  , Sink(..)
-  , Source(..)
-  , withActiveSocket
-  , withPassiveSocket
+  ( ConnectionType(..)
+  , Socket(..)
+  , S.PortNumber
+  , connect
+  , close
+  , recv
+  , sendAll
   , toNetworkByteOrder
   ) where
 
-import           Control.Error
-import           Control.Monad.IO.Class     (liftIO)
+import           Control.Monad.IO.Class     (MonadIO, liftIO)
 import           Data.Binary.Put            (putWord32be, runPut)
 import           Data.ByteString.Char8      (ByteString)
 import qualified Data.ByteString.Lazy.Char8 as Lazy (toStrict)
-import           Data.IP                    (IPv4, fromHostAddress,
-                                             toHostAddress)
-import           Network.Socket             hiding (recv, recvFrom, send,
-                                             sendTo)
-import           Network.Socket.ByteString
-import           System.Timeout
-
-data Sink = Sink IPv4 PortNumber
-
-data Source = Source IPv4 (Maybe PortNumber)
-
--- | Run functions on socket when connected and close socket afterwards
-withActiveSocket :: Sink
-                 -> (PortNumber -> ExceptT String IO ())
-                 -- ^ Callback when socket is ready
-                 -> (Socket -> IO ())
-                 -- ^ Callback when socket is connected to server
-                 -> ExceptT String IO ()
-withActiveSocket (Sink i p) onListen onConnected = do
-    liftIO $ return withSocketsDo
-    sock <- liftIO $ socket AF_INET Stream defaultProtocol
-    onListen p
-    liftIO $ connect sock (SockAddrInet p (toHostAddress i))
-    liftIO $ onConnected sock
-    liftIO $ sClose sock
-
-{- | Run functions on passive socket when listening and when connected and 
close
-     socket afterwards.
--}
-withPassiveSocket :: Source
-                  -> (PortNumber -> ExceptT String IO ())
-                  -- ^ Callback when socket is open and listening
-                  -> (Socket -> IO ())
-                  -- ^ Callback when client connected to socket
-                  -> ExceptT String IO ()
-withPassiveSocket (Source i maybeP) onListen onConnected = do
-    liftIO $ return withSocketsDo
-    sock <- liftIO $ openListenSocket (fromMaybe aNY_PORT maybeP)
-    p <- liftIO $ socketPort sock
-    onListen p
-    accepted <- liftIO $ timeout 10000000 $ accept sock
+import           Data.IP                    (IPv4, toHostAddress)
+import           Data.Maybe                 (fromMaybe)
+import qualified Network.Socket             as S hiding (recv, recvFrom, send,
+                                                  sendTo)
+import qualified Network.Socket.ByteString  as S
+
+data ConnectionType m
+  = Active !IPv4 !S.PortNumber (m ())
+  -- ^ Connects to other party on specified port. With callback when socket is 
ready.
+  | Passive !IPv4 !(Maybe S.PortNumber) (S.PortNumber -> m ())
+  -- ^ Binds to local port and waits for connection by other party. If no port 
number
+  -- is provided, one will be provided by the OS. With callback when socket is 
ready.
+
+data Socket
+  = ActiveSocket !S.Socket
+  | PassiveSocket !S.Socket !S.PortNumber
+
+socket :: Socket -> S.Socket
+socket (ActiveSocket sock)    = sock
+socket (PassiveSocket sock _) = sock
+
+connect :: MonadIO m => ConnectionType m -> m Socket
+connect (Active ip port onListen) = do
+    sock <- liftIO $ connectTo ip port
+    onListen
+    liftIO $ waitForConnection ip sock
+connect (Passive ip maybePort onListen) = do
+    sock@(PassiveSocket _ port) <- liftIO $ listenOn maybePort
+    onListen port
+    liftIO $ waitForConnection ip sock
+
+close :: Socket -> IO ()
+close = S.close . socket
+
+recv :: Socket -> Int -> IO ByteString
+recv = S.recv . socket
+
+sendAll :: Socket -> ByteString -> IO ()
+sendAll = S.sendAll . socket
+
+connectTo :: IPv4 -> S.PortNumber -> IO Socket
+connectTo ip port = S.withSocketsDo $ do
+    sock <- S.socket S.AF_INET S.Stream S.defaultProtocol
+    S.connect sock (S.SockAddrInet port (toHostAddress ip))
+    return $ ActiveSocket sock
+
+listenOn :: Maybe S.PortNumber -> IO Socket
+listenOn port = S.withSocketsDo $ do
+    sock  <- openListenSocket (fromMaybe S.aNY_PORT port)
+    port' <- S.socketPort sock
+    return $ PassiveSocket sock port'
+
+waitForConnection :: IPv4 -> Socket -> IO Socket
+waitForConnection _  sock@(ActiveSocket _)     = return sock -- Already 
connected
+waitForConnection ip (PassiveSocket sock port) = do
+    accepted <- S.accept sock
     case accepted of
-      Just (con, SockAddrInet _ client)
-        | client == toHostAddress i -> liftIO $ do onConnected con
-                                                   sClose con
-        | otherwise -> do liftIO $ sClose con
-                          throwE ( "Expected connection from host "
-                                ++ show (fromHostAddress client)
-                                ++ ", not from " ++ show i ++". Aborting…\n" )
-      _ -> throwE ( "Timeout when waiting for other party to connect on port "
-                 ++ show p ++ "…\n")
-    liftIO $ sClose sock
+      (con, S.SockAddrInet _ client)
+        | client == toHostAddress ip -> return $ PassiveSocket con port
+      _ -> fail ( "Connection did not come from " ++ show ip ++ " as 
expected." )
 
-openListenSocket :: PortNumber -> IO Socket
+openListenSocket :: S.PortNumber -> IO S.Socket
 openListenSocket p = do
-    sock <- socket AF_INET Stream defaultProtocol
-    bind sock (SockAddrInet p iNADDR_ANY)
-    listen sock 1
+    sock <- S.socket S.AF_INET S.Stream S.defaultProtocol
+    S.bind sock (S.SockAddrInet p S.iNADDR_ANY)
+    S.listen sock 1
     return sock
 
 -- | Converts numbers to a '32bit unsigned int' in network byte order.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/irc-dcc-1.2.1/src/System/IO/Streams/Lifted.hs 
new/irc-dcc-2.0.1/src/System/IO/Streams/Lifted.hs
--- old/irc-dcc-1.2.1/src/System/IO/Streams/Lifted.hs   1970-01-01 
01:00:00.000000000 +0100
+++ new/irc-dcc-2.0.1/src/System/IO/Streams/Lifted.hs   2016-08-21 
18:21:49.000000000 +0200
@@ -0,0 +1,32 @@
+-- | Some functions generalized from "System.IO.Streams"
+module System.IO.Streams.Lifted
+  ( withBinaryFile
+  , withFileAsOutputExt
+  ) where
+
+import           Control.Exception.Safe (MonadMask, bracket)
+import           Control.Monad.IO.Class (MonadIO, liftIO)
+import           Data.ByteString.Char8  (ByteString)
+import           System.IO              (BufferMode, Handle, IOMode, hClose,
+                                         hSetBuffering, openBinaryFile)
+import           System.IO.Streams      (OutputStream, handleToOutputStream)
+
+withBinaryFile :: (MonadMask m, MonadIO m)
+                => FilePath
+                -> IOMode
+                -> (Handle -> m a)
+                -> m a
+withBinaryFile name mode =
+    bracket (liftIO $ openBinaryFile name mode)
+            (liftIO . hClose)
+
+withFileAsOutputExt :: (MonadMask m, MonadIO m)
+                     => FilePath
+                     -> IOMode
+                     -> BufferMode
+                     -> (OutputStream ByteString -> m a)
+                     -> m a
+withFileAsOutputExt name mode buffermode f =
+    withBinaryFile name mode $ \h -> do
+        liftIO $ hSetBuffering h buffermode
+        liftIO (handleToOutputStream h) >>= f
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/irc-dcc-1.2.1/test/Network/IRC/DCCTest.hs 
new/irc-dcc-2.0.1/test/Network/IRC/DCCTest.hs
--- old/irc-dcc-1.2.1/test/Network/IRC/DCCTest.hs       1970-01-01 
01:00:00.000000000 +0100
+++ new/irc-dcc-2.0.1/test/Network/IRC/DCCTest.hs       2016-08-21 
18:21:49.000000000 +0200
@@ -0,0 +1,166 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Network.IRC.DCCTest where
+
+import           Network.IRC.DCC.Internal
+
+import           Control.Monad            (replicateM)
+import           Data.ByteString.Char8    (pack)
+import           Data.IP                  (IPv4, toIPv4)
+import           Network.Socket           (PortNumber)
+import           Path                     (mkAbsFile, mkRelFile)
+import           Test.Hspec.Attoparsec
+import           Test.Tasty               (TestTree)
+import           Test.Tasty.Hspec         (describe, it, testSpec)
+import           Test.Tasty.QuickCheck    (Arbitrary (..), Gen, choose,
+                                           elements, forAll, oneof, property)
+
+instance Arbitrary Token where
+  arbitrary = Token . pack <$> arbitrary
+
+instance Arbitrary PortNumber where
+  arbitrary = fromIntegral <$> (choose (1, 65535) :: Gen Int)
+
+instance Arbitrary IPv4 where
+  arbitrary = toIPv4 <$> replicateM 4 (choose (0, 255))
+
+instance Arbitrary Path where
+  arbitrary = elements [ Rel Simple $(mkRelFile "filename")
+                       , Rel Simple $(mkRelFile "filename.txt")
+                       , Rel Simple $(mkRelFile "file\8195name.txt")
+                       , Rel Simple $(mkRelFile "\128110\127997")
+                       , Abs Simple $(mkAbsFile 
"/home/user/dirname/filename.txt")
+                       , Rel Quoted $(mkRelFile "filename.txt")
+                       , Rel Quoted $(mkRelFile "file name.txt")
+                       , Rel Quoted $(mkRelFile "file\8195 name.txt")
+                       , Rel Quoted $(mkRelFile "\128110\127997")
+                       , Abs Quoted $(mkAbsFile "/home/user/dir 
name/filename.txt")
+                       ]
+
+instance Arbitrary FileOffset where
+  arbitrary = FileOffset <$> arbitrary
+
+instance Arbitrary DccChat where
+  arbitrary = oneof [ Chat <$> arbitrary <*> arbitrary
+                    , ChatWhiteboard <$> arbitrary <*> arbitrary
+                    ]
+
+instance Arbitrary DccSend where
+  arbitrary = oneof [ Send <$> arbitrary <*> arbitrary <*> arbitrary <*> 
arbitrary
+                    , SendReverseServer <$> arbitrary <*> arbitrary <*> 
arbitrary <*> arbitrary
+                    ]
+
+instance Arbitrary DccResume where
+  arbitrary = oneof [ Resume <$> arbitrary <*> arbitrary <*> arbitrary
+                    , ResumeReverse <$> arbitrary <*> arbitrary <*> arbitrary
+                    ]
+
+instance Arbitrary DccAccept where
+  arbitrary = oneof [ Accept <$> arbitrary <*> arbitrary <*> arbitrary
+                    , AcceptReverse <$> arbitrary <*> arbitrary <*> arbitrary
+                    ]
+
+instance Arbitrary DccSendReverseClient where
+  arbitrary = SendReverseClient
+      <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+
+spec :: IO TestTree
+spec = testSpec "DCC message serialization" $ do
+
+  describe "Parsing single elements" $ do
+
+    describe "Network byte order IP" $ do
+      describe "[SUCCESS]" $ do
+        it "parse . encode == id" $ property $ \ip ->
+          ipToBigEndianBS ip ~> ipBigEndian `parseSatisfies` (== ip)
+        it "At beginning of stream" $
+          pack "0abcd" ~?> ipBigEndian
+              `leavesUnconsumed` pack "abcd"
+      describe "[FAILURE]" $ do
+        it "Parse < 0.0.0.0" $
+          forAll (choose (minBound, -1 :: Int)) $ \ip ->
+             ipBigEndian `shouldFailOn` pack (show ip)
+        it "Parse > 255.255.255.255" $
+          forAll (choose (4294967296, maxBound :: Int)) $ \ip ->
+             ipBigEndian `shouldFailOn` pack (show ip)
+        it "Non-digits" $
+          ipBigEndian `shouldFailOn` pack "abcd"
+        it "When not at beginning of stream" $
+          ipBigEndian `shouldFailOn` pack " 0"
+
+    describe "File name" $ do
+      describe "[SUCCESS]" $ do
+        it "parse . encode == id" $ property $ \name ->
+          pathToBS name ~> path `parseSatisfies` (== name)
+        it "At beginning of stream" $
+          pack "filename 122350" ~?> path
+            `leavesUnconsumed` pack " 122350"
+-- TODO Not sure exactly what to do with this yet. On Unix the whole thing
+--      could be a file name, while on windows it is obviously an absolute path
+--                 it "File name from absolute unix path" $
+--                     ("c:\\Users\\user\\filename.txt" ~> path
+--                         `shouldParse` $(mkRelFile "filename.txt")
+      describe "[FAILURE]" $ do
+        it "ASCII filename with space" $
+          pack "file name.txt" ~?> path
+            `leavesUnconsumed` pack " name.txt"
+        it "Not at beginning of stream" $
+          path `shouldFailOn` pack " filename"
+
+    describe "Token" $
+      describe "[SUCCESS]" $
+        it "parse . encode == id" $ property $ \t ->
+          tokenToBS t ~> token `parseSatisfies` (== t)
+
+    describe "TCP port" $ do
+      describe "[SUCCESS]" $
+        it "parse . encode == id" $ property $ \p ->
+          tcpPortToBS p ~> tcpPort `parseSatisfies` (== p)
+      describe "[FAILURE]" $ do
+        it "Parse <= 0" $
+          forAll (choose (minBound, 0 :: Int)) $ \p ->
+             tcpPort `shouldFailOn` pack (show p)
+        it "Parse > 65535" $
+          forAll (choose (65535, maxBound :: Int)) $ \p ->
+             tcpPort `shouldFailOn` pack (show p)
+
+    describe "File offset" $ do
+      describe "[SUCCESS]" $
+        it "parse . encode == id" $ property $ \o ->
+          fileOffsetToBS o ~> fileOffset `parseSatisfies` (== o)
+      describe "[FAILURE]" $
+        it "Parse < 0" $
+          forAll (choose (minBound, -1 :: Int)) $ \o ->
+             fileOffset `shouldFailOn` pack (show o)
+
+  describe "DCC commands" $ do
+
+    describe "DCC CHAT" $
+      describe "[SUCCESS]" $
+        it "fromCtcp . toCtcp == id" $ property $ \cmd ->
+          fromCtcp (toCtcp cmd) == Right (cmd :: DccChat)
+
+    describe "DCC CLOSE" $
+      describe "[SUCCESS]" $
+        it "fromCtcp . toCtcp == id" $
+          fromCtcp (toCtcp Close) == Right Close
+
+    describe "DCC SEND" $
+      describe "[SUCCESS]" $
+        it "fromCtcp . toCtcp == id" $ property $ \cmd ->
+          fromCtcp (toCtcp cmd) == Right (cmd :: DccSend)
+
+    describe "DCC RESUME" $
+      describe "[SUCCESS]" $
+        it "fromCtcp . toCtcp == id" $ property $ \cmd ->
+          fromCtcp (toCtcp cmd) == Right (cmd :: DccResume)
+
+    describe "DCC ACCEPT" $
+      describe "[SUCCESS]" $
+        it "fromCtcp . toCtcp == id" $ property $ \cmd ->
+          fromCtcp (toCtcp cmd) == Right (cmd :: DccAccept)
+
+    describe "DCC SEND (reverse client)" $
+      describe "[SUCCESS]" $
+        it "fromCtcp . toCtcp == id" $ property $ \cmd ->
+          fromCtcp (toCtcp cmd) == Right (cmd :: DccSendReverseClient)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/irc-dcc-1.2.1/test/Spec.hs 
new/irc-dcc-2.0.1/test/Spec.hs
--- old/irc-dcc-1.2.1/test/Spec.hs      1970-01-01 01:00:00.000000000 +0100
+++ new/irc-dcc-2.0.1/test/Spec.hs      2016-08-21 18:21:49.000000000 +0200
@@ -0,0 +1,5 @@
+import qualified Network.IRC.DCCTest as DCC
+import           Test.Tasty
+
+main :: IO ()
+main = DCC.spec >>= defaultMain
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/irc-dcc-1.2.1/tests/Main.hs 
new/irc-dcc-2.0.1/tests/Main.hs
--- old/irc-dcc-1.2.1/tests/Main.hs     2016-04-07 16:26:16.000000000 +0200
+++ new/irc-dcc-2.0.1/tests/Main.hs     1970-01-01 01:00:00.000000000 +0100
@@ -1,7 +0,0 @@
-module Main where
-
-import qualified Network.IRC.DCCTest as DCC
-import           Test.Tasty
-
-main :: IO ()
-main = DCC.spec >>= defaultMain
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/irc-dcc-1.2.1/tests/Network/IRC/DCCTest.hs 
new/irc-dcc-2.0.1/tests/Network/IRC/DCCTest.hs
--- old/irc-dcc-1.2.1/tests/Network/IRC/DCCTest.hs      2016-04-07 
16:27:48.000000000 +0200
+++ new/irc-dcc-2.0.1/tests/Network/IRC/DCCTest.hs      1970-01-01 
01:00:00.000000000 +0100
@@ -1,118 +0,0 @@
-{-# LANGUAGE TemplateHaskell #-}
-
-module Network.IRC.DCCTest where
-
-import           Network.IRC.DCC.Internal
-
-import           Data.ByteString.Char8    (pack)
-import qualified Data.ByteString.UTF8     as UTF8 (fromString)
-import           Data.IP                  (toIPv4)
-import           Path                     (mkRelFile)
-import           Test.Hspec.Attoparsec
-import           Test.Tasty               (TestTree)
-import           Test.Tasty.Hspec         (describe, it, testSpec)
-
-spec :: IO TestTree
-spec = testSpec "DCC message serialization" $
-
-    describe "Parsing single elements" $ do
-
-        describe "Network byte order IP" $ do
-
-            describe "[SUCCESS]" $ do
-
-                it "Min IPv4" $
-                   pack "0" ~> parseIpBigEndian
-                       `shouldParse` toIPv4 [0, 0, 0, 0]
-
-                it "Max IPv4" $
-                   pack "4294967295" ~> parseIpBigEndian
-                       `shouldParse` toIPv4 [255, 255, 255, 255]
-
-                it "Local IPv4" $
-                   pack "3232235521" ~> parseIpBigEndian
-                       `shouldParse` toIPv4 [192, 168, 0, 1]
-
-                it "Public IPv4" $
-                   pack "134743044" ~> parseIpBigEndian
-                       `shouldParse` toIPv4 [8, 8, 4, 4]
-
-                it "IPv4 at beginning of stream" $
-                   pack "0abcd" ~?> parseIpBigEndian
-                       `leavesUnconsumed` pack "abcd"
-
-            describe "[FAILURE]" $ do
-
-                it "Negative IPv4" $
-                   parseIpBigEndian `shouldFailOn` pack "-1"
-
-                it "Bigger than max IPv4" $
-                   parseIpBigEndian `shouldFailOn` pack "4294967296"
-
-                it "Max IPv4 with additional digit" $
-                   parseIpBigEndian `shouldFailOn` pack "42949672950"
-
-                it "Non-digits" $
-                   parseIpBigEndian `shouldFailOn` pack "abcd"
-
-                it "When not at beginning of stream" $
-                   parseIpBigEndian `shouldFailOn` pack " 0"
-
-        describe "File name" $ do
-
-            describe "[SUCCESS]" $ do
-
-                it "Without extension" $
-                    pack "filename" ~> parseFileName
-                        `shouldParse` $(mkRelFile "filename")
-
-                it "With extension" $
-                    pack "filename.txt" ~> parseFileName
-                        `shouldParse` $(mkRelFile "filename.txt")
-
-                it "Quoted with extension" $
-                    pack "\"filename.txt\"" ~> parseFileName
-                        `shouldParse` $(mkRelFile "filename.txt")
-
-                it "Quoted with space" $
-                    pack "\"file name.txt\"" ~> parseFileName
-                        `shouldParse` $(mkRelFile "file name.txt")
-
-                it "UTF8 with em space" $
-                    UTF8.fromString "file\8195name.txt" ~> parseFileName
-                        `shouldParse` $(mkRelFile "file\8195name.txt")
-
-                it "UTF8 with skin tone modifier" $
-                    UTF8.fromString "\128110\127997" ~> parseFileName
-                        `shouldParse` $(mkRelFile "\128110\127997")
-
-                it "Quoted UTF8 with space" $
-                    UTF8.fromString "\"file\8195 name.txt\"" ~> parseFileName
-                        `shouldParse` $(mkRelFile "file\8195 name.txt")
-
-                it "From absolute unix path" $
-                    pack "/home/user/filename.txt" ~> parseFileName
-                        `shouldParse` $(mkRelFile "filename.txt")
-
-                it "From quoted absolute unix path" $
-                    pack "\"/home/user/filename.txt\"" ~> parseFileName
-                        `shouldParse` $(mkRelFile "filename.txt")
-
-                it "At beginning of stream" $
-                    pack "filename 122350" ~?> parseFileName
-                        `leavesUnconsumed` pack " 122350"
-
--- TODO Not sure exactly what to do with this yet. On Unix the whole thing
---      could be a file name, while on windows it is obviously an absolute path
---                 it "File name from absoulte unix path" $
---                     ("c:\\Users\\user\\filename.txt" ~> parseFileName
---                         `shouldParse` $(mkRelFile "filename.txt")
-
-            describe "[FAILURE]" $ do
-
-                it "ASCII filename with space" $
-                    pack "file name.txt" ~?> parseFileName
-                        `leavesUnconsumed` pack " name.txt"
-
-                it "Not at beginning of stream" $
-                    parseFileName `shouldFailOn` pack " filename"


Reply via email to