Compare commits

...

36 Commits

Author SHA1 Message Date
4a2eb43b89
update go 1.24 2025-04-27 13:25:18 +02:00
b8619306e9
remove escaping for quote
not required anymore
2024-03-03 17:46:30 +01:00
7969ec305f
change upload stats report 2024-03-03 17:15:38 +01:00
2df5388b4c
add goterminal and colortext 2024-03-03 17:07:08 +01:00
d571801ebc
fix login 2024-03-03 16:07:10 +01:00
4a351d5523
remove useless anonymous func 2024-03-03 16:07:04 +01:00
d716a4cfbd
fix category listing 2024-03-03 15:55:58 +01:00
9887338714
add idea config 2024-03-03 12:32:04 +01:00
910d0cc3f5
lint ignore 2024-03-03 12:31:26 +01:00
f8b76a3725
avoid having package name in local struct 2024-03-03 12:28:00 +01:00
d1dc0e72f6
remove camel case 2024-03-03 12:23:49 +01:00
b60ae43bf3
remove defer close in a loop 2024-03-03 12:22:49 +01:00
9015f2e8e5
remove unused params 2024-03-03 12:13:28 +01:00
8c77cb4506
fix deprecated 2024-03-03 12:11:14 +01:00
efc5a9793e
handle or ignore error 2024-03-03 12:09:04 +01:00
40a79e8e6b
defer lock and handle errors 2024-03-03 12:05:44 +01:00
a9254b7977
remove reserved word 2024-03-03 11:49:55 +01:00
9e64227494
factor keys 2024-03-03 11:49:35 +01:00
c29eec06be
improve help error 2024-03-03 10:53:04 +01:00
b6a8f50868
fix help error 2024-03-03 10:52:18 +01:00
04c3f1430c
fix typo 2024-03-03 10:39:10 +01:00
a96b94f9d0
fix grammar 2024-03-03 10:25:29 +01:00
06d690d493
update deps 2024-03-03 10:25:19 +01:00
48c383d8b2
use retry from post 2023-01-08 16:32:26 +01:00
b98102a596
upgrade deps 2023-01-08 13:08:45 +01:00
130d7d58de
Update README.md 2022-01-28 09:09:43 +01:00
8076159844
update doc 2022-01-27 20:22:12 +01:00
6b1365ea6d
update doc 2022-01-27 20:09:34 +01:00
6a69ed6626
update doc 2022-01-27 20:08:05 +01:00
457443f78b
update doc 2022-01-27 20:03:00 +01:00
f76d6bb2f7
move doc to readme 2022-01-27 20:01:38 +01:00
90bf9a8b03
add images details usage 2022-01-27 19:13:37 +01:00
1d9577448f
add images list doc 2022-01-07 08:53:46 +01:00
8040081cfe
add tree doc 2022-01-05 09:39:13 +01:00
45bfc16e7c
fix dump doc 2022-01-05 09:39:08 +01:00
b17aaf4f1d
update readme 2022-01-05 09:21:46 +01:00
38 changed files with 611 additions and 289 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="GoMixedReceiverTypes" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
</profile>
</component>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/piwigo-cli.iml" filepath="$PROJECT_DIR$/.idea/piwigo-cli.iml" />
</modules>
</component>
</project>

9
.idea/piwigo-cli.iml generated Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

222
README.md
View File

@ -1,59 +1,237 @@
# piwigo-cli
# Piwigo Cli
## Installation
This tools allow you to interact with your Piwigo Instance.
## Install
Installation:
```
go install github.com/celogeek/piwigo-cli/cmd/piwigo-cli@latest
$ go install github.com/celogeek/piwigo-cli/cmd/piwigo-cli@latest
```
## Quickstart
## Help
To get help:
```
$ piwigo-cli -h
Usage:
piwigo-cli [OPTIONS] <command>
Help Options:
-h, --help Show this help message
Available commands:
categories Categories management
getinfos Get general information
images Images management
method Reflexion management
session Session management
```
## QuickStart
### Login
First connect to your instance:
```
piwigo-cli session login -u URL -l USER -p PASSWORD
$ piwigo-cli session login -u URL -l USER -p PASSWORD
```
### Check your status
### Status
Then check your status
```
piwigo-cli session status
$ piwigo-cli session status
```
### List Categories
## Commands
### General commands
```
piwigo-cli categories list
$ piwigo-cli getinfos
```
### List images in a category and sub categories
### GetInfos Command
General information of your instance.
```
piwigo-cli images list -c 4 -r
$ piwigo-cli getinfos
┌───────────────────┬─────────────────────┐
│ KEY │ VALUE │
├───────────────────┼─────────────────────┤
│ version │ 12.2.0 │
│ nb_elements │ 39664 │
│ nb_categories │ 816 │
│ nb_virtual │ 816 │
│ nb_physical │ 0 │
│ nb_image_category │ 39714 │
│ nb_tags │ 73 │
│ nb_image_tag │ 24024 │
│ nb_users │ 3 │
│ nb_groups │ 1 │
│ nb_comments │ 0 │
│ first_date │ 2021-08-27 20:15:15 │
│ cache_size │ 4242 │
└───────────────────┴─────────────────────┘
```
With a tree style
### Categories List Command
List the categories.
```
piwigo-cli images list -c 4 -r -t
$ piwigo-cli categories list
┌──────┬───────────────────────────┬────────┬──────────────┬──────────────────────────────────────────────────────┐
│ ID │ NAME │ IMAGES │ TOTAL IMAGES │ URL │
├──────┼───────────────────────────┼────────┼──────────────┼──────────────────────────────────────────────────────┤
│ 4 │ 2021 │ 0 │ 1520 │ https://yourphotos/index.php?/category/4 │
│ 677 │ 2021/Animals │ 0 │ 49 │ https://yourphotos/index.php?/category/677 │
│ 24 │ 2021/Animals/Cats │ 29 │ 32 │ https://yourphotos/index.php?/category/24 │
│ 760 │ 2021/Animals/Cats/Videos │ 3 │ 3 │ https://yourphotos/index.php?/category/760 │
└──────┴───────────────────────────┴────────┴──────────────┴──────────────────────────────────────────────────────┘
```
### Upload a tree of images
### Images List Command
This will also create all the categories using the directory name.
List the images of a category.
Recursive list:
In this example, the category 4 (-c 4) is 2021
```
images upload-tree -d ~/Downloads/2021 -j4 -c 4
$ piwigo-cli images list -r
Category1/SubCategory1/IMG_00001.jpeg
Category1/SubCategory1/IMG_00002.jpeg
Category1/SubCategory1/IMG_00003.jpeg
Category1/SubCategory1/IMG_00004.jpeg
Category1/SubCategory2/IMG_00005.jpeg
Category1/SubCategory2/IMG_00006.jpeg
Category2/SubCategory1/IMG_00007.jpeg
```
This will create the categories based on your local directories, and upload only the images that doen't already exists somewhere else.
Specify a category:
```
$ piwigo-cli images list -r -c 2
The check of the images existance use MD5 checksum. If you change the metadata of the images, it will reupload the image as a new one.
Category2/SubCategory1/IMG_00007.jpeg
```
Tree view:
```
$ piwigo-cli images list -r -c 1 -t
.
├── SubCategory1
│ ├── IMG_00001.jpeg
│ ├── IMG_00002.jpeg
│ ├── IMG_00003.jpeg
│ └── IMG_00004.jpeg
└── SubCategory2
├── IMG_00005.jpeg
└── IMG_00006.jpeg
```
### Images Details Command
Get details of an image. It supports the birthday plugin.
```
$ piwigo-cli images details -i 38062
┌───────────────┬───────────────────────────────────────────────────────────────────────────┐
│ KEY │ VALUE │
├───────────────┼───────────────────────────────────────────────────────────────────────────┤
│ Id │ 38062 │
│ Md5 │ 6ad2abade6d5460181890e2bad671002 │
│ Name │ 2006 04 14 015 │
│ DateAvailable │ 2021-11-25 20:25:05 │
│ DateCreation │ 2006-04-14 04:14:00 │
│ LastModified │ 2022-01-01 23:11:48 │
│ Width │ 1984 │
│ Height │ 1488 │
│ Url │ https://yourphotos/picture.php?/38062 │
│ ImageUrl │ https://yourphotos/upload/2021/11/25/20211125202505-6ad2abad.jpg │
│ Filename │ 2006_04_14_015.jpeg │
│ Filesize │ 513 │
│ Categories │ 2007 │
│ Tags │ User Tag 1 (46 years old) │
│ │ User Tag 2 (8 months old) │
│ │ User Tag 3 (48 years old) │
└───────────────┴───────────────────────────────────────────────────────────────────────────┘
Derivatives:
┌─────────┬───────┬────────┬──────────────────────────────────────────────────────────────────────────────────────┐
│ NAME │ WIDTH │ HEIGHT │ URL │
├─────────┼───────┼────────┼──────────────────────────────────────────────────────────────────────────────────────┤
│ thumb │ 144 │ 108 │ https://yourphotos/i.php?/upload/2021/11/25/20211125202505-6ad2abad-th.jpg │
│ xsmall │ 432 │ 324 │ https://yourphotos/i.php?/upload/2021/11/25/20211125202505-6ad2abad-xs.jpg │
│ xxlarge │ 1656 │ 1242 │ https://yourphotos/_data/i/upload/2021/11/25/20211125202505-6ad2abad-xx.jpg │
│ square │ 120 │ 120 │ https://yourphotos/_data/i/upload/2021/11/25/20211125202505-6ad2abad-sq.jpg │
│ small │ 576 │ 432 │ https://yourphotos/_data/i/upload/2021/11/25/20211125202505-6ad2abad-sm.jpg │
│ medium │ 792 │ 594 │ https://yourphotos/_data/i/upload/2021/11/25/20211125202505-6ad2abad-me.jpg │
│ large │ 1008 │ 756 │ https://yourphotos/i.php?/upload/2021/11/25/20211125202505-6ad2abad-la.jpg │
│ xlarge │ 1224 │ 918 │ https://yourphotos/_data/i/upload/2021/11/25/20211125202505-6ad2abad-xl.jpg │
│ 2small │ 240 │ 180 │ https://yourphotos/i.php?/upload/2021/11/25/20211125202505-6ad2abad-2s.jpg │
└─────────┴───────┴────────┴──────────────────────────────────────────────────────────────────────────────────────┘
```
### Images Upload Command
Upload an image or a video in chunks. It will skip existing file on the server.
```
$ piwigo-cli images upload -f ~/Downloads/IMG_4886.jpeg -j 4
```
### Images Upload Tree Command
Upload a tree of images and videos in chunks, skipping existing file on the server.
```
$ piwigo-cli images upload-tree -d ~/Downloads/2021 -j4 -c 4
```
### Images Tag Command
Massive tag your image in your terminal.
```
$ piwigo-cli images tag -h
Usage:
piwigo-cli [OPTIONS] images tag [tag-OPTIONS]
Help Options:
-h, --help Show this help message
[tag command options]
-i, --id= image id to tag
-t, --tag-id= look up for the first image of this tagId
-T, --tag= look up for the first image of this tagName
-x, --exclude= exclude tag from selection
-m, --max= loop on a maximum number of images (default: 1)
-k, --keep keep survey filter
-K, --keep-previous-answer Preserve previous answer
```
Example:
Retag the image you mark as "todo:todo-2006", 50 max at a time, by keeping the previous selection between images.
```
$ piwigo-cli images tag -x ^todo -T todo:todo-2006 -m 50 -K
```
It displays in well on iTerm:
- image
- some details with the previous tag
- list of tags selection
You can use "SPACE" for selection of a tag, "LEFT" to remove the current selection, type words to lookup.
![visualtag](https://user-images.githubusercontent.com/65178/151510534-1c029d2b-f3a4-4fef-a4ae-8d831077a387.jpg)
You can remove the duplicates in piwigo by looking for similar photo with Name & Date & Size. Just pickup the first one so when you try to upload again, it won't reupload the images.
## License

View File

@ -7,5 +7,8 @@ type CategoriesGroup struct {
var categoriesGroup CategoriesGroup
func init() {
parser.AddCommand("categories", "Categories management", "", &categoriesGroup)
_, err := parser.AddCommand("categories", "Categories management", "", &categoriesGroup)
if err != nil {
panic(err)
}
}

View File

@ -13,7 +13,7 @@ type CategoriesListCommand struct {
Empty bool `short:"e" long:"empty" description:"Find empty album without any photo or sub album"`
}
func (c *CategoriesListCommand) Execute(args []string) error {
func (c *CategoriesListCommand) Execute([]string) error {
p := piwigo.Piwigo{}
if err := p.LoadConfig(); err != nil {
return err

View File

@ -1,21 +1,13 @@
// Piwigo Cli
//
// This tools allow you to interact with your Piwigo Instance.
// Installation:
// go install github.com/celogeek/piwigo-cli/cmd/piwigo-cli@latest
//
// Help
//
// To get help:
// piwigo-cli -h
//
// QuickStart
//
// Login
//
// First connect to your instance:
// piwigo-cli session login -u URL -l USER -p PASSWORD
//
// Then check your status
// piwigo-cli session status
/*
Piwigo Cli
This tools allow you to interact with your Piwigo Instance.
Installation:
go install github.com/celogeek/piwigo-cli/cmd/piwigo-cli@latest
Checkout the readme for usage example.
https://github.com/celogeek/piwigo-cli/#readme
*/
package main

View File

@ -1,27 +1,3 @@
// General Commands
//
// getinfos
//
// piwigo-cli getinfos
//
// General information of your instance.
// ┌───────────────────┬─────────────────────┐
// │ KEY │ VALUE │
// ├───────────────────┼─────────────────────┤
// │ version │ 12.2.0 │
// │ nb_elements │ 39664 │
// │ nb_categories │ 816 │
// │ nb_virtual │ 816 │
// │ nb_physical │ 0 │
// │ nb_image_category │ 39714 │
// │ nb_tags │ 73 │
// │ nb_image_tag │ 24024 │
// │ nb_users │ 3 │
// │ nb_groups │ 1 │
// │ nb_comments │ 0 │
// │ first_date │ 2021-08-27 20:15:15 │
// │ cache_size │ 4242 │
// └───────────────────┴─────────────────────┘
package main
import (
@ -48,7 +24,7 @@ type Info struct {
var getInfosCommand GetInfosCommand
func (c *GetInfosCommand) Execute(args []string) error {
func (c *GetInfosCommand) Execute([]string) error {
p := piwigo.Piwigo{}
if err := p.LoadConfig(); err != nil {
return err
@ -79,5 +55,8 @@ func (c *GetInfosCommand) Execute(args []string) error {
}
func init() {
parser.AddCommand("getinfos", "Get general information", "", &getInfosCommand)
_, err := parser.AddCommand("getinfos", "Get general information", "", &getInfosCommand)
if err != nil {
panic(err)
}
}

View File

@ -11,5 +11,8 @@ type ImagesGroup struct {
var imagesGroup ImagesGroup
func init() {
parser.AddCommand("images", "Images management", "", &imagesGroup)
_, err := parser.AddCommand("images", "Images management", "", &imagesGroup)
if err != nil {
panic(err)
}
}

View File

@ -15,7 +15,7 @@ type ImageDetailsCommand struct {
Id int `short:"i" long:"id" description:"ID of the images" required:"true"`
}
func (c *ImageDetailsCommand) Execute(args []string) error {
func (c *ImageDetailsCommand) Execute([]string) error {
p := piwigo.Piwigo{}
if err := p.LoadConfig(); err != nil {
return err
@ -33,7 +33,7 @@ func (c *ImageDetailsCommand) Execute(args []string) error {
return err
}
categories, err := p.Categories()
categories, err := p.CategoryFromId()
if err != nil {
return err
}

View File

@ -30,7 +30,7 @@ type ImagesListResult struct {
} `json:"paging"`
}
func (c *ImagesListCommand) Execute(args []string) error {
func (c *ImagesListCommand) Execute([]string) error {
p := piwigo.Piwigo{}
if err := p.LoadConfig(); err != nil {
return err
@ -64,7 +64,7 @@ func (c *ImagesListCommand) Execute(args []string) error {
bar := progressbar.Default(1, "listing")
progressbar.OptionOnCompletion(func() {
os.Stderr.WriteString("\n")
_, _ = os.Stderr.WriteString("\n")
})(bar)
for page := 0; ; page++ {
var resp ImagesListResult
@ -95,14 +95,14 @@ func (c *ImagesListCommand) Execute(args []string) error {
}
rootTree.AddPath(filename)
}
bar.Add(1)
_ = bar.Add(1)
}
if resp.Paging.Count < resp.Paging.PerPage {
break
}
}
bar.Close()
_ = bar.Close()
var results chan string
if c.Tree {

View File

@ -24,7 +24,7 @@ type ImagesTagCommand struct {
KeepPreviousAnswer bool `short:"K" long:"keep-previous-answer" description:"Preserve previous answer"`
}
func (c *ImagesTagCommand) Execute(args []string) error {
func (c *ImagesTagCommand) Execute([]string) error {
if c.MaxImages < 0 || c.MaxImages > 100 {
return fmt.Errorf("maxImages should be between 1 and 100")
}
@ -134,7 +134,7 @@ func (c *ImagesTagCommand) Execute(args []string) error {
if len(sel) == 0 {
exit := false
survey.AskOne(&survey.Confirm{
_ = survey.AskOne(&survey.Confirm{
Message: "Selection is empty, exit:",
Default: false,
}, &exit)
@ -144,7 +144,7 @@ func (c *ImagesTagCommand) Execute(args []string) error {
}
confirmSel := false
survey.AskOne(&survey.Confirm{
_ = survey.AskOne(&survey.Confirm{
Message: "Confirm:",
Default: true,
}, &confirmSel)

View File

@ -15,7 +15,7 @@ type ImagesUploadCommand struct {
CategoryId int `short:"c" long:"category" description:"Category to upload the file"`
}
func (c *ImagesUploadCommand) Execute(args []string) error {
func (c *ImagesUploadCommand) Execute([]string) error {
p := piwigo.Piwigo{}
if err := p.LoadConfig(); err != nil {
return err

View File

@ -11,7 +11,7 @@ type ImagesUploadTreeCommand struct {
CategoryId int `short:"c" long:"category" description:"Category to upload the file" required:"true"`
}
func (c *ImagesUploadTreeCommand) Execute(args []string) error {
func (c *ImagesUploadTreeCommand) Execute([]string) error {
p := piwigo.Piwigo{}
if err := p.LoadConfig(); err != nil {
return err
@ -26,12 +26,12 @@ func (c *ImagesUploadTreeCommand) Execute(args []string) error {
stat := piwigotools.NewFileToUploadStat()
defer stat.Close()
filesToCheck := make(chan *piwigotools.FileToUpload, 1000)
files := make(chan *piwigotools.FileToUpload, 1000)
filesToCheck := make(chan *piwigotools.FileToUpload, 10000)
files := make(chan *piwigotools.FileToUpload, 10000)
go p.ScanTree(c.Dirname, c.CategoryId, 0, &status.UploadFileType, stat, filesToCheck)
go p.CheckFiles(filesToCheck, files, stat, 8)
p.UploadFiles(files, stat, hasVideoJS, 4, 2)
p.UploadFiles(files, stat, hasVideoJS, c.NbJobs, 2)
return nil
}

View File

@ -1,6 +1,7 @@
package main
import (
"errors"
"os"
"github.com/jessevdk/go-flags"
@ -15,14 +16,10 @@ var parser = flags.NewParser(&options, flags.Default)
func main() {
if _, err := parser.Parse(); err != nil {
switch flagsErr := err.(type) {
case flags.ErrorType:
if flagsErr == flags.ErrHelp {
os.Exit(0)
}
os.Exit(1)
default:
os.Exit(1)
var flagsErr *flags.Error
if errors.As(err, &flagsErr) && errors.Is(flagsErr.Type, flags.ErrHelp) {
os.Exit(0)
}
os.Exit(1)
}
}

View File

@ -9,5 +9,8 @@ type MethodGroup struct {
var methodGroup MethodGroup
func init() {
parser.AddCommand("method", "Reflexion management", "", &methodGroup)
_, err := parser.AddCommand("method", "Reflexion management", "", &methodGroup)
if err != nil {
panic(err)
}
}

View File

@ -14,7 +14,7 @@ type MethodDetailsCommand struct {
MethodName string `short:"m" long:"method-name" description:"Method name to details"`
}
func (c *MethodDetailsCommand) Execute(args []string) error {
func (c *MethodDetailsCommand) Execute([]string) error {
p := piwigo.Piwigo{}
if err := p.LoadConfig(); err != nil {
return err

View File

@ -57,7 +57,7 @@ type MethodDetails struct {
Parameters MethodParams `json:"params"`
}
func (c *MethodListCommand) Execute(args []string) error {
func (c *MethodListCommand) Execute([]string) error {
p := piwigo.Piwigo{}
if err := p.LoadConfig(); err != nil {
return err

View File

@ -50,7 +50,7 @@ func ArgsToForm(args []string) (*url.Values, error) {
if len(r) != 2 {
return nil, errors.New("args should be key=value")
}
params.Add(r[0], strings.ReplaceAll(r[1], "'", `\'`))
params.Add(r[0], r[1])
}
return params, nil
}

View File

@ -8,5 +8,8 @@ type SessionGroup struct {
var sessionGroup SessionGroup
func init() {
parser.AddCommand("session", "Session management", "", &sessionGroup)
_, err := parser.AddCommand("session", "Session management", "", &sessionGroup)
if err != nil {
panic(err)
}
}

View File

@ -12,7 +12,7 @@ type SessionLoginCommand struct {
Password string `short:"p" long:"password" description:"Password" required:"true"`
}
func (c *SessionLoginCommand) Execute(args []string) error {
func (c *SessionLoginCommand) Execute([]string) error {
fmt.Printf("Login on %s...\n", c.Url)
p := piwigo.Piwigo{

View File

@ -10,7 +10,7 @@ import (
type SessionStatusCommand struct {
}
func (c *SessionStatusCommand) Execute(args []string) error {
func (c *SessionStatusCommand) Execute([]string) error {
p := piwigo.Piwigo{}
if err := p.LoadConfig(); err != nil {
return err

31
go.mod
View File

@ -1,26 +1,27 @@
module github.com/celogeek/piwigo-cli
go 1.17
go 1.24
require (
github.com/AlecAivazis/survey/v2 v2.3.2
github.com/barasher/go-exiftool v1.7.0
github.com/grokify/html-strip-tags-go v0.0.1
github.com/jedib0t/go-pretty/v6 v6.2.4
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/apoorvam/goterminal v0.0.0-20180523175556-614d345c47e5
github.com/barasher/go-exiftool v1.10.0
github.com/daviddengcn/go-colortext v1.0.0
github.com/grokify/html-strip-tags-go v0.1.0
github.com/jedib0t/go-pretty/v6 v6.5.4
github.com/jessevdk/go-flags v1.5.0
github.com/schollz/progressbar/v3 v3.8.5
golang.org/x/text v0.3.7
github.com/schollz/progressbar/v3 v3.14.2
golang.org/x/text v0.14.0
)
require (
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/rivo/uniseg v0.2.0 // indirect
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/term v0.17.0 // indirect
)

109
go.sum
View File

@ -1,74 +1,95 @@
github.com/AlecAivazis/survey/v2 v2.3.2 h1:TqTB+aDDCLYhf9/bD2TwSO8u8jDSmMUd2SUVO4gCnU8=
github.com/AlecAivazis/survey/v2 v2.3.2/go.mod h1:TH2kPCDU3Kqq7pLbnCWwZXDBjnhZtmsCle5EiYDJ2fg=
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
github.com/barasher/go-exiftool v1.7.0 h1:EOGb5D6TpWXmqsnEjJ0ai6+tIW2gZFwIoS9O/33Nixs=
github.com/barasher/go-exiftool v1.7.0/go.mod h1:F9s/a3uHSM8YniVfwF+sbQUtP8Gmh9nyzigNF+8vsWo=
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/apoorvam/goterminal v0.0.0-20180523175556-614d345c47e5 h1:VYqcjykqpcq262cDxBAkAelSdg6HETkxgwzQRTS40Aw=
github.com/apoorvam/goterminal v0.0.0-20180523175556-614d345c47e5/go.mod h1:E7x8aDc3AQzDKjEoIZCt+XYheHk2OkP+p2UgeNjecH8=
github.com/barasher/go-exiftool v1.10.0 h1:f5JY5jc42M7tzR6tbL9508S2IXdIcG9QyieEXNMpIhs=
github.com/barasher/go-exiftool v1.10.0/go.mod h1:F9s/a3uHSM8YniVfwF+sbQUtP8Gmh9nyzigNF+8vsWo=
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E=
github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0=
github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
github.com/jedib0t/go-pretty/v6 v6.2.4 h1:wdaj2KHD2W+mz8JgJ/Q6L/T5dB7kyqEFI16eLq7GEmk=
github.com/jedib0t/go-pretty/v6 v6.2.4/go.mod h1:+nE9fyyHGil+PuISTCrp7avEdo6bqoMwqZnuiK2r2a0=
github.com/daviddengcn/go-colortext v1.0.0 h1:ANqDyC0ys6qCSvuEK7l3g5RaehL/Xck9EX8ATG8oKsE=
github.com/daviddengcn/go-colortext v1.0.0/go.mod h1:zDqEI5NVUop5QPpVJUxE9UO10hRnmkD5G4Pmri9+m4c=
github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho=
github.com/golangplus/bytes v1.0.0/go.mod h1:AdRaCFwmc/00ZzELMWb01soso6W1R/++O1XL80yAn+A=
github.com/golangplus/fmt v1.0.0/go.mod h1:zpM0OfbMCjPtd2qkTD/jX2MgiFCqklhSUFyDW44gVQE=
github.com/golangplus/testing v1.0.0 h1:+ZeeiKZENNOMkTTELoSySazi+XaEhVO0mb+eanrSEUQ=
github.com/golangplus/testing v1.0.0/go.mod h1:ZDreixUV3YzhoVraIDyOzHrr76p6NUh6k/pPg/Q3gYA=
github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
github.com/jedib0t/go-pretty/v6 v6.5.4 h1:gOGo0613MoqUcf0xCj+h/V3sHDaZasfv152G6/5l91s=
github.com/jedib0t/go-pretty/v6 v6.5.4/go.mod h1:5LQIxa52oJ/DlDSLv0HEkWOFMDGoWkJb9ss5KqPpJBg=
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ=
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/schollz/progressbar/v3 v3.8.5 h1:VcmmNRO+eFN3B0m5dta6FXYXY+MEJmXdWoIS+jjssQM=
github.com/schollz/progressbar/v3 v3.8.5/go.mod h1:ewO25kD7ZlaJFTvMeOItkOZa8kXu1UvFs379htE8HMQ=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/schollz/progressbar/v3 v3.14.2 h1:EducH6uNLIWsr560zSV1KrTeUb/wZGAHqyMFIEa99ks=
github.com/schollz/progressbar/v3 v3.14.2/go.mod h1:aQAZQnhF4JGFtRJiw/eobaXpsqpVQAftEQ+hLGXaRc4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,7 +1,7 @@
/*
Debug tools
debug.Dump(myStruct)
fmt.Println(debug.Dump(myStruct))
*/
package debug
@ -10,7 +10,7 @@ import (
)
/*
Dump an interface to the stdout
Dump an interface into a json string format
*/
func Dump(v interface{}) string {
result, err := json.MarshalIndent(v, "", " ")

View File

@ -2,10 +2,9 @@ package piwigo
import (
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"strings"
"sync"
"github.com/celogeek/piwigo-cli/internal/piwigo/piwigotools"
@ -60,15 +59,15 @@ func (p *Piwigo) Upload(file *piwigotools.FileToUpload, stat *piwigotools.FileTo
return
}
wg := &sync.WaitGroup{}
chunks, err := file.Base64Chunker()
chunks, err := file.Base64BuildChunk()
if err != nil {
stat.Error("Base64Chunker", *file.FullPath(), err)
stat.Error("Base64BuildChunk", *file.FullPath(), err)
return
}
ok := true
wg.Add(nbJobs)
for j := 0; j < nbJobs; j++ {
for range nbJobs {
go p.UploadChunk(file, chunks, wg, stat, &ok)
}
wg.Wait()
@ -92,16 +91,13 @@ func (p *Piwigo) Upload(file *piwigotools.FileToUpload, stat *piwigotools.FileTo
p.mu.Lock()
defer p.mu.Unlock()
for i := 0; i < 3; i++ {
err = p.Post("pwg.images.add", data, &resp)
if err == nil || err.Error() == "[Error 500] file already exists" {
err = nil
break
}
stat.Error(fmt.Sprintf("Upload %d", i), *file.FullPath(), err)
err = p.Post("pwg.images.add", data, &resp)
if err == nil || err.Error() == "[Error 500] file already exists" {
err = nil
}
if err != nil {
stat.Error("Upload", *file.FullPath(), err)
stat.Fail()
return
}
@ -109,7 +105,9 @@ func (p *Piwigo) Upload(file *piwigotools.FileToUpload, stat *piwigotools.FileTo
if hasVideoJS {
switch *file.Ext() {
case "ogg", "ogv", "mp4", "m4v", "webm", "webmv":
p.VideoJSSync(resp.ImageId)
if err := p.VideoJSSync(resp.ImageId); err != nil {
stat.Error("VideoJSSync", *file.FullPath(), err)
}
}
}
@ -126,15 +124,12 @@ func (p *Piwigo) UploadChunk(file *piwigotools.FileToUpload, chunks chan *piwigo
"type": []string{"file"},
"data": []string{chunk.Buffer.String()},
}
for i := 0; i < 3; i++ {
err = p.Post("pwg.images.addChunk", data, nil)
if err == nil {
break
}
stat.Error(fmt.Sprintf("UploadChunk %d", i), *file.FullPath(), err)
}
err = p.Post("pwg.images.addChunk", data, nil)
stat.Commit(chunk.Size)
if err != nil {
stat.Error("UploadChunk", *file.FullPath(), err)
stat.Fail()
*ok = false
return
@ -165,7 +160,7 @@ func (p *Piwigo) ScanTree(
return
}
dirs, err := ioutil.ReadDir(rootPath)
dirs, err := os.ReadDir(rootPath)
if err != nil {
stat.Error("ScanTree Dir", rootPath, err)
return
@ -180,7 +175,7 @@ func (p *Piwigo) ScanTree(
category = &piwigotools.Category{}
p.mu.Lock()
err = p.Post("pwg.categories.add", &url.Values{
"name": []string{strings.ReplaceAll(dirname, "'", `\'`)},
"name": []string{dirname},
"parent": []string{fmt.Sprint(parentCategoryId)},
}, &category)
p.mu.Unlock()

View File

@ -23,17 +23,17 @@ func (p *Piwigo) GetStatus() (*StatusResponse, error) {
resp := &StatusResponse{}
err := p.Post("pwg.session.getStatus", nil, resp)
if err != nil {
if err := p.Post("pwg.session.getStatus", nil, resp); err != nil {
return nil, err
} else if resp.User != p.Username {
return nil, errors.New("you are a guest")
}
if err := p.Post("pwg.plugins.getList", nil, &resp.Plugins); err != nil {
return nil, err
}
p.Post("pwg.plugins.getList", nil, &resp.Plugins)
if resp.User == p.Username {
return resp, nil
}
return nil, errors.New("you are a guest")
return resp, nil
}
func (p *Piwigo) Login() (*StatusResponse, error) {

View File

@ -26,18 +26,18 @@ func (uft *ActivePlugin) UnmarshalJSON(data []byte) error {
return nil
}
func (uft ActivePlugin) MarshalJSON() ([]byte, error) {
func (uft ActivePlugin) keys() []string {
keys := make([]string, 0, len(uft))
for k := range uft {
keys = append(keys, k)
}
return json.Marshal(keys)
return keys
}
func (uft ActivePlugin) MarshalJSON() ([]byte, error) {
return json.Marshal(uft.keys())
}
func (uft ActivePlugin) String() string {
keys := make([]string, 0, len(uft))
for k := range uft {
keys = append(keys, k)
}
return strings.Join(keys, ",")
return strings.Join(uft.keys(), ",")
}

View File

@ -75,6 +75,7 @@ func (f *FileToUpload) MD5() *string {
if err != nil {
return nil
}
//goland:noinspection GoUnhandledErrorResult
defer file.Close()
hash := md5.New()
if _, err = io.Copy(hash, file); err != nil {
@ -95,6 +96,7 @@ func (f *FileToUpload) CreatedAt() *TimeResult {
if err != nil {
return nil
}
//goland:noinspection GoUnhandledErrorResult
defer et.Close()
var createdAt *time.Time
@ -143,9 +145,9 @@ func (f *FileToUpload) Checked() bool {
}
var (
CHUNK_SIZE int64 = 1 * 1024 * 1024
CHUNK_BUFF_SIZE int64 = 32 * 1024
CHUNK_BUFF_COUNT = CHUNK_SIZE / CHUNK_BUFF_SIZE
ChunkSize int64 = 1 * 1024 * 1024
ChunkBuffSize int64 = 32 * 1024
ChunkBuffCount = ChunkSize / ChunkBuffSize
)
type FileToUploadChunk struct {
@ -154,15 +156,16 @@ type FileToUploadChunk struct {
Buffer bytes.Buffer
}
func (f *FileToUpload) Base64Chunker() (chan *FileToUploadChunk, error) {
func (f *FileToUpload) Base64BuildChunk() (chan *FileToUploadChunk, error) {
fh, err := os.Open(*f.FullPath())
if err != nil {
return nil, err
}
out := make(chan *FileToUploadChunk, 8)
chunker := func() {
b := make([]byte, CHUNK_BUFF_SIZE)
go func() {
b := make([]byte, ChunkBuffSize)
//goland:noinspection GoUnhandledErrorResult
defer fh.Close()
defer close(out)
ok := false
@ -171,23 +174,21 @@ func (f *FileToUpload) Base64Chunker() (chan *FileToUploadChunk, error) {
Position: position,
}
b64 := base64.NewEncoder(base64.StdEncoding, &bf.Buffer)
for i := int64(0); i < CHUNK_BUFF_COUNT; i++ {
for i := int64(0); i < ChunkBuffCount; i++ {
n, _ := fh.Read(b)
if n == 0 {
ok = true
break
}
bf.Size += int64(n)
b64.Write(b[:n])
_, _ = b64.Write(b[:n])
}
b64.Close()
_ = b64.Close()
if bf.Size > 0 {
out <- bf
}
}
}
go chunker()
}()
return out, nil
}

View File

@ -2,104 +2,145 @@ package piwigotools
import (
"fmt"
"github.com/apoorvam/goterminal"
ct "github.com/daviddengcn/go-colortext"
"os"
"strings"
"sync"
"time"
)
"github.com/schollz/progressbar/v3"
const (
megabytes = 1 << 20
kilobytes = 1 << 10
)
type FileToUploadStat struct {
Checked int64
Total int64
TotalBytes int64
Uploaded int64
Checked uint32
Total uint32
Uploaded uint32
Skipped uint32
Failed uint32
UploadedBytes int64
Skipped int64
Failed int64
Progress *progressbar.ProgressBar
mu sync.Mutex
TotalBytes int64
progress *goterminal.Writer
mu sync.Mutex
lastRefresh time.Time
}
func NewFileToUploadStat() *FileToUploadStat {
bar := progressbar.DefaultBytes(1, "...")
progressbar.OptionOnCompletion(func() { os.Stderr.WriteString("\n") })(bar)
return &FileToUploadStat{
Progress: bar,
progress: goterminal.New(os.Stdout),
}
}
func (s *FileToUploadStat) Refresh() {
s.Progress.Describe(fmt.Sprintf(
"%d / %d - check:%d, upload:%d, skip:%d, fail:%d",
s.Uploaded+s.Skipped+s.Failed,
s.Total,
s.Checked,
s.Uploaded,
s.Skipped,
s.Failed,
),
func (s *FileToUploadStat) formatBytes(b int64) string {
if b > megabytes {
return fmt.Sprintf("%.2fMB", float64(b)/(1<<20))
} else if b > kilobytes {
return fmt.Sprintf("%.2fKB", float64(b)/(1<<10))
} else {
return fmt.Sprintf("%dB", b)
}
}
func (s *FileToUploadStat) refreshIfNeeded() {
if time.Since(s.lastRefresh) > 200*time.Millisecond {
s.refresh()
}
}
func (s *FileToUploadStat) refresh() {
s.progress.Clear()
_, _ = s.progress.Write([]byte("Statistics:\n"))
_, _ = fmt.Fprintf(
s.progress,
strings.Repeat("%20s: %d\n", 5)+strings.Repeat("%20s: %s\n", 2),
"Checked", s.Checked,
"Uploaded", s.Uploaded,
"Skipped", s.Skipped,
"Failed", s.Failed,
"Total", s.Total,
"Uploaded Bytes", s.formatBytes(s.UploadedBytes),
"Total Bytes", s.formatBytes(s.TotalBytes),
)
_ = s.progress.Print()
s.lastRefresh = time.Now()
}
func (s *FileToUploadStat) Check() {
s.mu.Lock()
s.Checked++
s.Refresh()
s.mu.Unlock()
}
defer s.mu.Unlock()
func (s *FileToUploadStat) AddBytes(filesize int64) {
s.mu.Lock()
s.TotalBytes += filesize
s.Progress.ChangeMax64(s.TotalBytes + 1)
s.Refresh()
s.mu.Unlock()
s.Checked++
s.refreshIfNeeded()
}
func (s *FileToUploadStat) Add() {
s.mu.Lock()
defer s.mu.Unlock()
s.Total++
s.Refresh()
s.mu.Unlock()
s.refreshIfNeeded()
}
func (s *FileToUploadStat) Commit(filereaded int64) {
func (s *FileToUploadStat) AddBytes(filesize int64) {
s.mu.Lock()
s.UploadedBytes += filereaded
s.Progress.Set64(s.UploadedBytes)
s.mu.Unlock()
defer s.mu.Unlock()
s.TotalBytes += filesize
s.refreshIfNeeded()
}
func (s *FileToUploadStat) Commit(fileread int64) {
s.mu.Lock()
defer s.mu.Unlock()
s.UploadedBytes += fileread
s.refreshIfNeeded()
}
func (s *FileToUploadStat) Done() {
s.mu.Lock()
s.Uploaded++
s.Refresh()
s.mu.Unlock()
}
defer s.mu.Unlock()
func (s *FileToUploadStat) Close() {
s.Progress.Close()
s.Uploaded++
s.refreshIfNeeded()
}
func (s *FileToUploadStat) Fail() {
s.mu.Lock()
defer s.mu.Unlock()
s.Failed++
s.Refresh()
s.mu.Unlock()
s.refreshIfNeeded()
}
func (s *FileToUploadStat) Skip() {
s.mu.Lock()
defer s.mu.Unlock()
s.Skipped++
s.Refresh()
s.mu.Unlock()
s.refreshIfNeeded()
}
func (s *FileToUploadStat) Error(origin string, filename string, err error) error {
func (s *FileToUploadStat) Error(origin string, filename string, err error) {
s.mu.Lock()
s.Progress.Clear()
fmt.Printf("[%s] %s: %s\n", origin, filename, err)
s.Progress.RenderBlank()
s.mu.Unlock()
return err
defer s.mu.Unlock()
s.progress.Clear()
ct.Foreground(ct.Red, false)
_, _ = fmt.Fprintf(s.progress, "[%s] %s: %s\n", origin, filename, err)
_ = s.progress.Print()
ct.ResetColor()
s.progress.Reset()
s.refreshIfNeeded()
}
func (s *FileToUploadStat) Close() {
s.refresh()
}

View File

@ -43,6 +43,7 @@ func (img *ImageDetails) Preview(height int) (string, error) {
if resp.StatusCode != 200 {
return "", fmt.Errorf("[error %d] failed to get image", resp.StatusCode)
}
//goland:noinspection GoUnhandledErrorResult
defer resp.Body.Close()
buf := bytes.NewBuffer([]byte{})
@ -57,6 +58,7 @@ func (img *ImageDetails) Preview(height int) (string, error) {
buf.WriteString(":")
encoder := base64.NewEncoder(base64.StdEncoding, buf)
//goland:noinspection GoUnhandledErrorResult
defer encoder.Close()
if _, err := io.Copy(encoder, resp.Body); err != nil {
return "", err

View File

@ -63,11 +63,13 @@ func (t Tags) Selector(exclude *regexp.Regexp, keepFilter bool, keepPreviousAnsw
tags[tag.Name] = tag
}
//goland:noinspection GoPreferNilSlice
previousAnswer := []string{}
return func() Tags {
//goland:noinspection GoPreferNilSlice
answer := []string{}
survey.AskOne(&survey.MultiSelect{
_ = survey.AskOne(&survey.MultiSelect{
Message: "Tags:",
Options: options,
PageSize: 20,

View File

@ -45,7 +45,7 @@ func (c TimeResult) toTime() time.Time {
}
func (c TimeResult) AgeAt(createdAt *TimeResult) string {
var year, month, day, hour, min, sec int
var year, month, day, hour, minutes, seconds int
a := c.toTime()
if a.IsZero() {
return ""
@ -67,20 +67,20 @@ func (c TimeResult) AgeAt(createdAt *TimeResult) string {
h1, m1, s1 := a.Clock()
h2, m2, s2 := b.Clock()
year = int(y2 - y1)
year = y2 - y1
month = int(M2 - M1)
day = int(d2 - d1)
hour = int(h2 - h1)
min = int(m2 - m1)
sec = int(s2 - s1)
day = d2 - d1
hour = h2 - h1
minutes = m2 - m1
seconds = s2 - s1
// Normalize negative values
if sec < 0 {
sec += 60
min--
if seconds < 0 {
seconds += 60
minutes--
}
if min < 0 {
min += 60
if minutes < 0 {
minutes += 60
hour--
}
if hour < 0 {

View File

@ -9,11 +9,12 @@ import (
"net/url"
"os"
"strings"
"time"
"github.com/celogeek/piwigo-cli/internal/debug"
)
type PiwigoResult struct {
type PostResult struct {
Stat string `json:"stat"`
Err int `json:"err"`
ErrMessage string `json:"message"`
@ -45,13 +46,17 @@ func (p *Piwigo) Post(method string, form *url.Values, resp interface{}) error {
encodedForm = form.Encode()
}
Result := PiwigoResult{
result := PostResult{
Result: resp,
}
raw := bytes.NewBuffer([]byte{})
for i := 0; i < 3; i++ {
for i := range 3 {
if i > 0 {
time.Sleep(time.Second) // wait 1 sec before retry
}
req, err := http.NewRequest("POST", Url, strings.NewReader(encodedForm))
if err != nil {
return err
@ -66,15 +71,16 @@ func (p *Piwigo) Post(method string, form *url.Values, resp interface{}) error {
if err != nil {
continue
}
defer r.Body.Close()
_, err = io.Copy(raw, r.Body)
if err != nil {
_ = r.Body.Close()
continue
}
err = json.Unmarshal(raw.Bytes(), &Result)
err = json.Unmarshal(raw.Bytes(), &result)
if err != nil {
_ = r.Body.Close()
continue
}
@ -85,6 +91,7 @@ func (p *Piwigo) Post(method string, form *url.Values, resp interface{}) error {
}
}
_ = r.Body.Close()
break
}
@ -102,8 +109,8 @@ func (p *Piwigo) Post(method string, form *url.Values, resp interface{}) error {
fmt.Println(debug.Dump(RawResult))
}
if Result.Stat != "ok" {
return fmt.Errorf("[Error %d] %s", Result.Err, Result.ErrMessage)
if result.Stat != "ok" {
return fmt.Errorf("[Error %d] %s", result.Err, result.ErrMessage)
}
return nil

View File

@ -31,6 +31,7 @@ func (p *Piwigo) VideoJSSync(imageId int) error {
if err != nil {
return err
}
//goland:noinspection GoUnhandledErrorResult
defer r.Body.Close()
return nil
}

View File

@ -1,3 +1,22 @@
/*
Package tree builder and viewer
This allows you to create a tree of files and display them as a tree or flat view.
Example
t := tree.New()
t.AddPath("a/b/c/d")
t.AddPath("a/b/e/f")
for v := range t.FlatView() {
fmt.Println(v)
}
for v := range t.TreeView() {
fmt.Println(v)
}
*/
package tree
import (
@ -13,6 +32,7 @@ type Tree interface {
TreeView() chan string
}
// New create a new tree
func New() Tree {
return &node{
Name: ".",
@ -24,6 +44,20 @@ type node struct {
Nodes map[string]*node
}
/*
Add a node to your tree and return it
You can chain it:
t.Add("a").Add("b").Add("c").Add("d")
Or
c := t
for _, s := range strings.Split("a/b/c/d", "/") {
c = c.Add(s)
}
*/
func (t *node) Add(name string) Tree {
if t.Nodes == nil {
t.Nodes = map[string]*node{}
@ -35,6 +69,12 @@ func (t *node) Add(name string) Tree {
}
return n
}
/*
AddPath add a path to your tree, separated with /
t.AddPath("a/b/c/d")
*/
func (t *node) AddPath(path string) Tree {
n := Tree(t)
for _, name := range strings.Split(path, "/") {
@ -43,23 +83,32 @@ func (t *node) AddPath(path string) Tree {
return n
}
// Children return a sorted list of children
func (t *node) Children() []*node {
childs := make([]*node, len(t.Nodes))
children := make([]*node, len(t.Nodes))
i := 0
for _, n := range t.Nodes {
childs[i] = n
children[i] = n
i++
}
sort.Slice(childs, func(i, j int) bool {
return childs[i].Name < childs[j].Name
sort.Slice(children, func(i, j int) bool {
return children[i].Name < children[j].Name
})
return childs
return children
}
// HasChildren Check if your node has a children
func (t *node) HasChildren() bool {
return t.Nodes != nil
}
/*
FlatView return a flat view of your tree
for v := range t.FlatView() {
fmt.Println(v)
}
*/
func (t *node) FlatView() (out chan string) {
out = make(chan string)
go func() {
@ -82,6 +131,13 @@ func (t *node) FlatView() (out chan string) {
return out
}
/*
TreeView return a tree view of your tree
for v := range t.TreeView() {
fmt.Println(v)
}
*/
func (t *node) TreeView() (out chan string) {
out = make(chan string)
treeLinkChar := "│ "