18import qualified Codec.Archive.Zip as Zip
19import Control.Exception (catch)
20import qualified Data.Binary as Binary
21import qualified Data.ByteString as BS
22import qualified Data.ByteString.Internal as BS
23import qualified Data.ByteString.Builder as B
24import qualified Data.ByteString.Lazy as LBS
25import qualified Data.Fixed as Fixed
26import qualified Data.List as List
27import qualified Data.Time.Clock.POSIX as Time
28import qualified Foreign.ForeignPtr as FPtr
29import GHC.IO.Exception (IOException, IOErrorType(InvalidArgument))
30import qualified System.Directory as Dir
31import qualified System.FileLock as Lock
32import qualified System.FilePath as FP
33import System.FilePath ((</>))
34import qualified System.IO as IO
35import System.IO.Error (ioeGetErrorType, annotateIOError, modifyIOError)
42newtype Time = Time Fixed.Pico
46getTime :: FilePath -> IO Time
49 (Time . realToFrac . Time.utcTimeToPOSIXSeconds)
50-- TODO (Time . Time.nominalDiffTimeToSeconds . Time.utcTimeToPOSIXSeconds)
51 (Dir.getModificationTime path)
54instance Binary.Binary Time where
55 put (Time time) = Binary.put time
56 get = Time <$> Binary.get
63withLock :: FilePath -> IO a -> IO a
65 Lock.withFileLock path Lock.Exclusive (\_ -> work)
72writeBinary :: (Binary.Binary a) => FilePath -> a -> IO ()
73writeBinary path value =
74 do let dir = FP.dropFileName path
75 Dir.createDirectoryIfMissing True dir
76 Binary.encodeFile path value
79readBinary :: (Binary.Binary a) => FilePath -> IO (Maybe a)
81 do pathExists <- Dir.doesFileExist path
84 do result <- Binary.decodeFileOrFail path
86 Right a -> return (Just a)
87 Left e -> return Nothing
96writeUtf8 :: FilePath -> BS.ByteString -> IO ()
97writeUtf8 path content =
98 withUtf8 path IO.WriteMode $ \handle ->
99 BS.hPut handle content
102withUtf8 :: FilePath -> IO.IOMode -> (IO.Handle -> IO a) -> IO a
103withUtf8 path mode callback =
104 IO.withFile path mode $ \handle ->
105 do IO.hSetEncoding handle IO.utf8
113readUtf8 :: FilePath -> IO BS.ByteString
115 withUtf8 path IO.ReadMode $ \handle ->
116 modifyIOError (encodingError path) $
117 do fileSize <- catch (IO.hFileSize handle) useZeroIfNotRegularFile
118 let readSize = max 0 (fromIntegral fileSize) + 1
119 hGetContentsSizeHint handle readSize (max 255 readSize)
122useZeroIfNotRegularFile :: IOException -> IO Integer
123useZeroIfNotRegularFile _ =
127hGetContentsSizeHint :: IO.Handle -> Int -> Int -> IO BS.ByteString
128hGetContentsSizeHint handle =
131 readChunks chunks readSize incrementSize =
132 do fp <- BS.mallocByteString readSize
133 readCount <- FPtr.withForeignPtr fp $ \buf -> IO.hGetBuf handle buf readSize
134 let chunk = BS.PS fp 0 readCount
135 if readCount < readSize && readSize > 0
136 then return $! BS.concat (reverse (chunk:chunks))
137 else readChunks (chunk:chunks) incrementSize (min 32752 (readSize + incrementSize))
140encodingError :: FilePath -> IOError -> IOError
141encodingError path ioErr =
142 case ioeGetErrorType ioErr of
145 (userError "Bad encoding; the file must be valid UTF-8")
158writeBuilder :: FilePath -> B.Builder -> IO ()
159writeBuilder path builder =
160 IO.withBinaryFile path IO.WriteMode $ \handle ->
161 do IO.hSetBuffering handle (IO.BlockBuffering Nothing)
162 B.hPutBuilder handle builder
169writePackage :: FilePath -> Zip.Archive -> IO ()
170writePackage destination archive =
171 case Zip.zEntries archive of
176 do let root = length (Zip.eRelativePath entry)
177 mapM_ (writeEntry destination root) entries
180writeEntry :: FilePath -> Int -> Zip.Entry -> IO ()
181writeEntry destination root entry =
183 path = drop root (Zip.eRelativePath entry)
185 if List.isPrefixOf "src/" path then
186 if not (null path) && last path == '/'
187 then Dir.createDirectoryIfMissing True (destination </> path)
188 else LBS.writeFile (destination </> path) (Zip.fromEntry entry)
197exists :: FilePath -> IO Bool
199 Dir.doesFileExist path
206remove :: FilePath -> IO ()
208 do exists_ <- Dir.doesFileExist path
210 then Dir.removeFile path
214removeDir :: FilePath -> IO ()
216 do exists_ <- Dir.doesDirectoryExist path
218 then Dir.removeDirectoryRecursive path
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list