gopro-plus 0.2.0.0 → 0.3.0.0
raw patch · 4 files changed
+72/−36 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ GoPro.Plus.Media: Burst :: MediumType
+ GoPro.Plus.Media: Photo :: MediumType
+ GoPro.Plus.Media: TimeLapse :: MediumType
+ GoPro.Plus.Media: TimeLapseVideo :: MediumType
+ GoPro.Plus.Media: Video :: MediumType
+ GoPro.Plus.Media: ViewFailure :: ReadyToViewType
+ GoPro.Plus.Media: ViewLoading :: ReadyToViewType
+ GoPro.Plus.Media: ViewProcessing :: ReadyToViewType
+ GoPro.Plus.Media: ViewReady :: ReadyToViewType
+ GoPro.Plus.Media: ViewRegistered :: ReadyToViewType
+ GoPro.Plus.Media: ViewTranscoding :: ReadyToViewType
+ GoPro.Plus.Media: ViewUploading :: ReadyToViewType
+ GoPro.Plus.Media: data MediumType
+ GoPro.Plus.Media: data ReadyToViewType
+ GoPro.Plus.Media: instance Data.Aeson.Types.FromJSON.FromJSON GoPro.Plus.Media.MediumType
+ GoPro.Plus.Media: instance Data.Aeson.Types.FromJSON.FromJSON GoPro.Plus.Media.ReadyToViewType
+ GoPro.Plus.Media: instance Data.Aeson.Types.ToJSON.ToJSON GoPro.Plus.Media.MediumType
+ GoPro.Plus.Media: instance Data.Aeson.Types.ToJSON.ToJSON GoPro.Plus.Media.ReadyToViewType
+ GoPro.Plus.Media: instance GHC.Classes.Eq GoPro.Plus.Media.MediumType
+ GoPro.Plus.Media: instance GHC.Classes.Eq GoPro.Plus.Media.ReadyToViewType
+ GoPro.Plus.Media: instance GHC.Read.Read GoPro.Plus.Media.MediumType
+ GoPro.Plus.Media: instance GHC.Read.Read GoPro.Plus.Media.ReadyToViewType
+ GoPro.Plus.Media: instance GHC.Show.Show GoPro.Plus.Media.MediumType
+ GoPro.Plus.Media: instance GHC.Show.Show GoPro.Plus.Media.ReadyToViewType
+ GoPro.Plus.Upload: getUpload :: (HasGoProAuth m, MonadIO m) => UploadID -> DerivativeID -> Int -> Int -> Uploader m Upload
- GoPro.Plus.Auth: withAuth :: (Monad m, HasGoProAuth m) => AuthInfo -> AuthReader m a -> m a
+ GoPro.Plus.Auth: withAuth :: AuthInfo -> AuthReader m a -> m a
- GoPro.Plus.Media: Medium :: MediumID -> Maybe String -> UTCTime -> UTCTime -> Maybe Int -> Int -> String -> Maybe String -> String -> String -> Maybe Int -> Maybe Int -> Medium
+ GoPro.Plus.Media: Medium :: MediumID -> Maybe String -> UTCTime -> UTCTime -> Maybe Int -> Int -> ReadyToViewType -> Maybe String -> MediumType -> String -> Maybe Int -> Maybe Int -> Medium
- GoPro.Plus.Media: [_medium_ready_to_view] :: Medium -> String
+ GoPro.Plus.Media: [_medium_ready_to_view] :: Medium -> ReadyToViewType
- GoPro.Plus.Media: [_medium_type] :: Medium -> String
+ GoPro.Plus.Media: [_medium_type] :: Medium -> MediumType
- GoPro.Plus.Media: medium_ready_to_view :: Lens' Medium String
+ GoPro.Plus.Media: medium_ready_to_view :: Lens' Medium ReadyToViewType
- GoPro.Plus.Media: medium_type :: Lens' Medium String
+ GoPro.Plus.Media: medium_type :: Lens' Medium MediumType
- GoPro.Plus.Upload: setMediumType :: Monad m => Text -> Uploader m ()
+ GoPro.Plus.Upload: setMediumType :: Monad m => MediumType -> Uploader m ()
Files
- gopro-plus.cabal +2/−2
- src/GoPro/Plus/Auth.hs +6/−7
- src/GoPro/Plus/Media.hs +31/−7
- src/GoPro/Plus/Upload.hs +33/−20
gopro-plus.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 5e05f8a01680acbd25e66b1641f2c5a02799d5bf8e23075385e768184d45927c+-- hash: 4bb7255d2a4a022d5784c1045d89a42542f454252c098c0e9cd6953c1255c7a7 name: gopro-plus-version: 0.2.0.0+version: 0.3.0.0 synopsis: GoPro Plus Client API. description: Please see the README on GitHub at <https://github.com/dustin/gopro-plus#readme> category: Web
src/GoPro/Plus/Auth.hs view
@@ -9,11 +9,10 @@ GoPro Plus authentication. -} -{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TemplateHaskell #-} module GoPro.Plus.Auth ( authenticate, refreshAuth,@@ -83,7 +82,7 @@ goproAuth = ask -- | Convenient function for passing around auth info. You probably--- don't want to use this, but it can be conven ient when+-- don't want to use this, but it can be convenient when -- experimenting.-withAuth :: (Monad m, HasGoProAuth m) => AuthInfo -> AuthReader m a -> m a+withAuth :: AuthInfo -> AuthReader m a -> m a withAuth = flip runReaderT
src/GoPro/Plus/Media.hs view
@@ -20,7 +20,7 @@ fetchThumbnail, -- * Data Types PageInfo(..), current_page, per_page, total_items, total_pages,- MediumID,+ MediumID, MediumType(..), ReadyToViewType(..), Medium(..), medium_id, medium_camera_model, medium_captured_at, medium_created_at, medium_file_size, medium_moments_count, medium_ready_to_view, medium_source_duration, medium_type,@@ -49,8 +49,10 @@ genericParseJSON, genericToEncoding, genericToJSON, (.:))+import qualified Data.Aeson as J import Data.Aeson.Types (typeMismatch) import qualified Data.ByteString.Lazy as BL+import Data.Char (toLower, toUpper) import qualified Data.Map.Strict as Map import qualified Data.Text as T import Data.Time.Clock (UTCTime)@@ -78,6 +80,29 @@ type MediumID = T.Text +data MediumType = Photo | Video | TimeLapse | TimeLapseVideo | Burst+ deriving (Show, Read, Eq)++instance ToJSON MediumType where+ toJSON = J.String . T.pack . show++instance FromJSON MediumType where+ parseJSON (J.String x) = pure . read . T.unpack $ x+ parseJSON invalid = typeMismatch "Response" invalid++data ReadyToViewType = ViewReady | ViewFailure | ViewLoading+ | ViewRegistered | ViewTranscoding | ViewProcessing | ViewUploading+ deriving (Show, Read, Eq)++instance ToJSON ReadyToViewType where+ toJSON = J.String . T.pack . fmap toLower . drop 4 . show++instance FromJSON ReadyToViewType where+ parseJSON (J.String s) = pure . read . trans . T.unpack $ s+ where trans (x:xs) = "View" <> (toUpper x : xs)+ trans [] = error "empty ready to view type"+ parseJSON invalid = typeMismatch "Response" invalid+ data Medium = Medium { _medium_id :: MediumID, _medium_camera_model :: Maybe String,@@ -85,9 +110,9 @@ _medium_created_at :: UTCTime, _medium_file_size :: Maybe Int, _medium_moments_count :: Int,- _medium_ready_to_view :: String,+ _medium_ready_to_view :: ReadyToViewType, _medium_source_duration :: Maybe String,- _medium_type :: String,+ _medium_type :: MediumType, _medium_token :: String, _medium_width :: Maybe Int, _medium_height :: Maybe Int@@ -137,8 +162,8 @@ list :: (HasGoProAuth m, MonadIO m) => Int -> Int -> m ([Medium], PageInfo) list psize page = do r <- jgetAuth ("https://api.gopro.com/media/search?fields=captured_at,created_at,file_size,id,moments_count,ready_to_view,source_duration,type,token,width,height,camera_model&order_by=created_at&per_page=" <> show psize <> "&page=" <> show page)- pure $ (r ^.. media . folded,- r ^. pages)+ pure (r ^.. media . folded,+ r ^. pages) -- | List all media. listAll :: (HasGoProAuth m, MonadIO m) => m [Medium]@@ -146,8 +171,7 @@ -- | List all media while returned batches pass the given predicate. listWhile :: (HasGoProAuth m, MonadIO m) => ([Medium] -> Bool) -> m [Medium]-listWhile f = do- Map.elems <$> dig 0 mempty+listWhile f = Map.elems <$> dig 0 mempty where dig n m = do (ms, _) <- list 100 n
src/GoPro/Plus/Upload.hs view
@@ -20,7 +20,7 @@ -- * Low-level upload parts. runUpload, resumeUpload, createMedium, createSource, createDerivative, createUpload,- completeUpload, uploadChunk, markAvailable,+ completeUpload, getUpload, uploadChunk, markAvailable, -- * Data Types UploadID, DerivativeID, UploadPart(..), uploadLength, uploadPart, uploadURL,@@ -62,7 +62,9 @@ import GoPro.Plus.Auth (AuthInfo (..), HasGoProAuth (..)) import GoPro.Plus.Internal.AuthHTTP import GoPro.Plus.Internal.HTTP-import GoPro.Plus.Media (Medium (..), MediumID, list,+import GoPro.Plus.Media (Medium (..), MediumID,+ MediumType (..),+ ReadyToViewType (..), list, putMedium) type UploadID = T.Text@@ -83,7 +85,7 @@ data Env m = Env { fileList :: [FilePath],- mediumType :: T.Text,+ mediumType :: MediumType, extension :: T.Text, filename :: String, mediumID :: MediumID,@@ -92,15 +94,14 @@ -- | List all media in uploading state. listUploading :: (HasGoProAuth m, MonadIO m) => m [Medium]-listUploading = do- filter (\Medium{..} -> _medium_ready_to_view == "uploading") . fst <$> list 30 1+listUploading = filter (\Medium{..} -> _medium_ready_to_view == ViewUploading) . fst <$> list 30 1 -- | Run an Uploader monad to create a single medium and upload the content for it. runUpload :: (HasGoProAuth m, MonadFail m, MonadIO m) => [FilePath] -- ^ The list of files to include in the medium. -> Uploader m a -- ^ The action to perform. -> m a -- ^ The result of the inner action.-runUpload fileList a = resumeUpload fileList "" a+runUpload fileList = resumeUpload fileList "" -- | Run an Uploader monad for which we already know the MediumID -- (i.e., we're resuming an upload we previously began).@@ -114,11 +115,11 @@ mediumType = fileType extension logAction _ = pure () - fileType "JPG" = "Photo"- fileType _ = "Video"+ fileType "JPG" = Photo+ fileType _ = Video -- | Override the detected medium type.-setMediumType :: Monad m => T.Text -> Uploader m ()+setMediumType :: Monad m => MediumType -> Uploader m () setMediumType t = modify (\m -> m{mediumType=t}) -- | Set the logging action to report retries (or whatever other@@ -139,7 +140,7 @@ AuthInfo{..} <- goproAuth let m1 = J.Object (mempty & at "file_extension" ?~ J.String extension & at "filename" ?~ J.String (T.pack filename)- & at "type" ?~ J.String mediumType+ & at "type" ?~ J.toJSON mediumType & at "on_public_profile" ?~ J.Bool False & at "content_title" ?~ J.String (T.pack filename) & at "content_source" ?~ J.String "web_media_library"@@ -207,28 +208,40 @@ & at "access_token" ?~ J.String _access_token & at "gopro_user_id" ?~ J.String _resource_owner_id) ur <- jpost "https://api.gopro.com/user-uploads" u1- let Just upid = ur ^? key "id" . _String+ getUpload upid did part fsize++ where+ popts tok = authOpts tok & header "Accept" .~ ["application/vnd.gopro.jk.user-uploads+json; version=2.0.0"]+ jpost :: (HasGoProAuth m, MonadIO m) => String -> J.Value -> m J.Value+ jpost u p = (_access_token <$> goproAuth) >>= \tok -> jpostVal (popts tok) u p++getUpload :: (HasGoProAuth m, MonadIO m)+ => UploadID+ -> DerivativeID+ -> Int+ -> Int+ -> Uploader m Upload+getUpload upid did part fsize = do+ Env{..} <- get+ AuthInfo{..} <- goproAuth++ let pages = (ceiling ((fromIntegral fsize :: Double) / fromIntegral chunkSize)) :: Int upopts = authOpts _access_token & params .~ [("id", upid), ("page", "1"),- ("per_page", "100"),+ ("per_page", (T.pack . show) pages), ("item_number", (T.pack . show) part), ("camera_position", "default"), ("file_size", (T.pack . show) fsize), ("part_size", (T.pack . show) chunkSize)] & header "Accept" .~ ["application/vnd.gopro.jk.user-uploads+json; version=2.0.0"]- upaths <- (jgetWith upopts (T.unpack ("https://api.gopro.com/user-uploads/" <> did)))+ upaths <- jgetWith upopts (T.unpack ("https://api.gopro.com/user-uploads/" <> did)) let Just ups = (upaths :: J.Value) ^? key "_embedded" . key "authorizations" . _Array . to V.toList pure $ Upload upid (fromJust $ traverse aChunk ups) where- popts tok = authOpts tok & header "Accept" .~ ["application/vnd.gopro.jk.user-uploads+json; version=2.0.0"]- jpost :: (HasGoProAuth m, MonadIO m) => String -> J.Value -> m J.Value- jpost u p = (_access_token <$> goproAuth) >>= \tok -> jpostVal (popts tok) u p- tInt :: T.Text -> Integer tInt = read . T.unpack- aChunk v = liftA3 UploadPart (v ^? key "Content-Length" . _String . to tInt) (v ^? key "part" . _Integer . to toInteger) (v ^? key "url" . _String . to T.unpack)@@ -244,7 +257,7 @@ hSeek fh AbsoluteSeek ((_uploadPart - 1) * chunkSize) void $ putWith defOpts _uploadURL =<< BL.hGet fh (fromIntegral _uploadLength) - where policy = exponentialBackoff 2000000 <> limitRetries 5+ where policy = exponentialBackoff 2000000 <> limitRetries 9 retryMsg a = mconcat ["Retrying upload of ", show fp, " part ", show _uploadPart, " attempt ", show a] @@ -282,7 +295,7 @@ _ <- liftIO $ putWith (popts _access_token) (T.unpack ("https://api.gopro.com/derivatives/" <> did)) d2 - now <- liftIO $ getCurrentTime+ now <- liftIO getCurrentTime let done = J.Object (mempty & at "upload_completed_at" ?~ J.toJSON now & at "client_updated_at" ?~ J.toJSON now & at "revision_number" ?~ J.Number 0