checkpoint: can now download media from instance
This commit is contained in:
parent
67d4caf4cb
commit
a4872dff46
8 changed files with 205 additions and 45 deletions
|
@ -30,12 +30,14 @@ func main() {
|
|||
func run() error {
|
||||
var (
|
||||
configDir string
|
||||
cacheDir string
|
||||
pager string
|
||||
maxTerminalWidth int
|
||||
noColor *bool
|
||||
)
|
||||
|
||||
flag.StringVar(&configDir, "config-dir", "", "Specify your config directory")
|
||||
flag.StringVar(&cacheDir, "cache-dir", "", "Specify your cache directory")
|
||||
flag.StringVar(&pager, "pager", "", "Specify your preferred pager to page through long outputs. This is disabled by default.")
|
||||
flag.IntVar(&maxTerminalWidth, "max-terminal-width", 80, "Specify the maximum terminal width when displaying resources on screen.")
|
||||
|
||||
|
@ -170,6 +172,7 @@ func run() error {
|
|||
executor.CommandShow: executor.NewShowExecutor(
|
||||
printer,
|
||||
configDir,
|
||||
cacheDir,
|
||||
executor.CommandShow,
|
||||
executor.CommandSummaryLookup(executor.CommandShow),
|
||||
),
|
||||
|
|
|
@ -60,6 +60,10 @@ func (g *Client) AuthCodeURL() string {
|
|||
)
|
||||
}
|
||||
|
||||
func (g *Client) DownloadFile(url, path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Client) sendRequest(method string, url string, requestBody io.Reader, object any) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), g.Timeout)
|
||||
defer cancel()
|
||||
|
|
|
@ -11,6 +11,8 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -41,7 +43,7 @@ func (e CredentialsNotFoundError) Error() string {
|
|||
// directory. If the directory is not specified then the default directory is used. If the directory
|
||||
// is not present, it will be created.
|
||||
func SaveCredentials(configDir, username string, credentials Credentials) (string, error) {
|
||||
if err := ensureConfigDir(calculateConfigDir(configDir)); err != nil {
|
||||
if err := utilities.EnsureDirectory(utilities.CalculateConfigDir(configDir)); err != nil {
|
||||
return "", fmt.Errorf("unable to ensure the configuration directory: %w", err)
|
||||
}
|
||||
|
||||
|
@ -141,5 +143,5 @@ func saveCredentialsConfigFile(authConfig CredentialsConfig, configDir string) e
|
|||
}
|
||||
|
||||
func credentialsConfigFile(configDir string) string {
|
||||
return filepath.Join(calculateConfigDir(configDir), credentialsFileName)
|
||||
return filepath.Join(utilities.CalculateConfigDir(configDir), credentialsFileName)
|
||||
}
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2024 Dan Anglin <d.n.i.anglin@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"codeflow.dananglin.me.uk/apollo/enbas/internal"
|
||||
)
|
||||
|
||||
func calculateConfigDir(configDir string) string {
|
||||
if configDir != "" {
|
||||
return configDir
|
||||
}
|
||||
|
||||
rootDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
rootDir = "."
|
||||
}
|
||||
|
||||
return filepath.Join(rootDir, internal.ApplicationName)
|
||||
}
|
||||
|
||||
func ensureConfigDir(configDir string) error {
|
||||
if _, err := os.Stat(configDir); err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
if err := os.MkdirAll(configDir, 0o750); err != nil {
|
||||
return fmt.Errorf("unable to create %s: %w", configDir, err)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("unknown error received after getting the config directory information: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -5,8 +5,11 @@
|
|||
package executor
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
|
||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
|
||||
|
@ -23,6 +26,7 @@ type ShowExecutor struct {
|
|||
showUserPreferences bool
|
||||
showInBrowser bool
|
||||
configDir string
|
||||
cacheRoot string
|
||||
resourceType string
|
||||
accountName string
|
||||
statusID string
|
||||
|
@ -31,15 +35,17 @@ type ShowExecutor struct {
|
|||
tag string
|
||||
pollID string
|
||||
attachmentID string
|
||||
fromResourceType string
|
||||
limit int
|
||||
}
|
||||
|
||||
func NewShowExecutor(printer *printer.Printer, configDir, name, summary string) *ShowExecutor {
|
||||
func NewShowExecutor(printer *printer.Printer, configDir, cacheRoot, name, summary string) *ShowExecutor {
|
||||
showExe := ShowExecutor{
|
||||
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
|
||||
|
||||
printer: printer,
|
||||
configDir: configDir,
|
||||
cacheRoot: cacheRoot,
|
||||
}
|
||||
|
||||
showExe.BoolVar(&showExe.myAccount, flagMyAccount, false, "Set to true to lookup your account")
|
||||
|
@ -54,6 +60,7 @@ func NewShowExecutor(printer *printer.Printer, configDir, name, summary string)
|
|||
showExe.StringVar(&showExe.tag, flagTag, "", "Specify the name of the tag to use")
|
||||
showExe.StringVar(&showExe.pollID, flagPollID, "", "Specify the ID of the poll to display")
|
||||
showExe.StringVar(&showExe.attachmentID, flagAttachmentID, "", "Specify the ID of the media attachment to display")
|
||||
showExe.StringVar(&showExe.fromResourceType, flagFrom, "", "Specify the resource type to view the target resource from (e.g. status for viewing media from, etc)")
|
||||
showExe.IntVar(&showExe.limit, flagLimit, 20, "Specify the limit of items to display")
|
||||
|
||||
showExe.Usage = commandUsageFunc(name, summary, showExe.FlagSet)
|
||||
|
@ -81,7 +88,7 @@ func (s *ShowExecutor) Execute() error {
|
|||
resourceFollowRequest: s.showFollowRequests,
|
||||
resourcePoll: s.showPoll,
|
||||
resourceMutedAccounts: s.showMutedAccounts,
|
||||
resourceMedia: s.showMediaAttachment,
|
||||
resourceMedia: s.showMedia,
|
||||
resourceMediaAttachment: s.showMediaAttachment,
|
||||
}
|
||||
|
||||
|
@ -421,3 +428,81 @@ func (s *ShowExecutor) showMediaAttachment(gtsClient *client.Client) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ShowExecutor) showMedia(gtsClient *client.Client) error {
|
||||
if s.fromResourceType == "" {
|
||||
return FlagNotSetError{flagText: flagFrom}
|
||||
}
|
||||
|
||||
funcMap := map[string]func(*client.Client) error{
|
||||
resourceStatus: s.showMediaFromStatus,
|
||||
}
|
||||
|
||||
doFunc, ok := funcMap[s.fromResourceType]
|
||||
if !ok {
|
||||
return fmt.Errorf("do not support viewing media from %s", s.fromResourceType)
|
||||
}
|
||||
|
||||
return doFunc(gtsClient)
|
||||
}
|
||||
|
||||
func (s *ShowExecutor) showMediaFromStatus(gtsClient *client.Client) error {
|
||||
if s.attachmentID == "" {
|
||||
return FlagNotSetError{flagText: flagAttachmentID}
|
||||
}
|
||||
|
||||
if s.statusID == "" {
|
||||
return FlagNotSetError{flagText: flagStatusID}
|
||||
}
|
||||
|
||||
status, err := gtsClient.GetStatus(s.statusID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to retrieve the status: %w", err)
|
||||
}
|
||||
|
||||
attachmentExists := false
|
||||
mediaURL := ""
|
||||
mediaFilename := ""
|
||||
|
||||
for _, attachment := range status.MediaAttachments {
|
||||
if attachment.ID == s.attachmentID {
|
||||
mediaURL = attachment.URL
|
||||
split := strings.Split(attachment.URL, "/")
|
||||
mediaFilename = split[len(split)-1]
|
||||
attachmentExists = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !attachmentExists {
|
||||
return errors.New("this media is not attached to this status")
|
||||
}
|
||||
|
||||
cacheDir := filepath.Join(
|
||||
utilities.CalculateCacheDir(s.cacheRoot, utilities.GetFQDN(gtsClient.Authentication.Instance)),
|
||||
"media",
|
||||
)
|
||||
|
||||
if err := utilities.EnsureDirectory(cacheDir); err != nil {
|
||||
return fmt.Errorf("unable to ensure the existence of the directory %q: %w", cacheDir, err)
|
||||
}
|
||||
|
||||
mediaFilePath := filepath.Join(cacheDir, mediaFilename)
|
||||
|
||||
fileExists, err := utilities.FileExists(mediaFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to check if the media file is already downloaded: %w", err)
|
||||
}
|
||||
|
||||
if !fileExists {
|
||||
if err := utilities.DownloadFile(mediaURL, mediaFilePath); err != nil {
|
||||
return fmt.Errorf("unable to download the media attachment: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: display the file in the image viewer.
|
||||
fmt.Println("checkpoint: the media file is now on disk; just need to open it in an image viewer...")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
50
internal/utilities/directories.go
Normal file
50
internal/utilities/directories.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package utilities
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"codeflow.dananglin.me.uk/apollo/enbas/internal"
|
||||
)
|
||||
|
||||
func CalculateConfigDir(configDir string) string {
|
||||
if configDir != "" {
|
||||
return configDir
|
||||
}
|
||||
|
||||
configRoot, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
configRoot = "."
|
||||
}
|
||||
|
||||
return filepath.Join(configRoot, internal.ApplicationName)
|
||||
}
|
||||
|
||||
func CalculateCacheDir(cacheDir, instanceFQDN string) string {
|
||||
if cacheDir != "" {
|
||||
return cacheDir
|
||||
}
|
||||
|
||||
cacheRoot, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
cacheRoot = "."
|
||||
}
|
||||
|
||||
return filepath.Join(cacheRoot, internal.ApplicationName, instanceFQDN)
|
||||
}
|
||||
|
||||
func EnsureDirectory(dir string) error {
|
||||
if _, err := os.Stat(dir); err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
if err := os.MkdirAll(dir, 0o750); err != nil {
|
||||
return fmt.Errorf("unable to create %s: %w", dir, err)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("received an unknown error after getting the directory information: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -5,8 +5,12 @@
|
|||
package utilities
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ReadFile(path string) (string, error) {
|
||||
|
@ -17,3 +21,45 @@ func ReadFile(path string) (string, error) {
|
|||
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func FileExists(path string) (bool, error) {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("unable to check if the file exists: %w", err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// TODO: move to client package
|
||||
func DownloadFile(url, path string) error {
|
||||
client := http.Client{
|
||||
CheckRedirect: func(r *http.Request, _ []*http.Request) error {
|
||||
r.URL.Opaque = r.URL.Path
|
||||
|
||||
return nil
|
||||
},
|
||||
Timeout: time.Second * 30,
|
||||
}
|
||||
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to download the file: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create %s: %w", path, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if _, err = io.Copy(file, resp.Body); err != nil {
|
||||
return fmt.Errorf("unable to save the download to %s: %w", path, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
11
internal/utilities/utilities.go
Normal file
11
internal/utilities/utilities.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package utilities
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func GetFQDN(url string) string {
|
||||
r := regexp.MustCompile(`http(s)?:\/\/`)
|
||||
|
||||
return r.ReplaceAllString(url, "")
|
||||
}
|
Loading…
Reference in a new issue