jkawamoto/roadie

View on GitHub
command/data.go

Summary

Maintainability
A
3 hrs
Test Coverage
//
// command/data.go
//
// Copyright (c) 2016-2017 Junpei Kawamoto
//
// This file is part of Roadie.
//
// Roadie is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Roadie is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Roadie.  If not, see <http://www.gnu.org/licenses/>.
//

package command

import (
    "fmt"
    "net/url"
    "path"
    "path/filepath"
    "runtime"
    "sync"

    "golang.org/x/sync/errgroup"

    "github.com/jkawamoto/roadie/cloud"
    "github.com/jkawamoto/roadie/script"
    "github.com/urfave/cli"
)

// optDataPut defines options for data put command.
type optDataPut struct {
    *Metadata
    // Filename is a path for an actual file or a glob pattern.
    Filename string
    // StoredName is a path where the file to be stored.
    // If Filename is a glob pattern, matched files will be uploaded in a
    // directory named by this argument.
    StoredName string
}

// run defines process of data put command.
func (o *optDataPut) run() (err error) {

    filenames, err := filepath.Glob(o.Filename)
    if err != nil {
        return
    } else if len(filenames) == 0 {
        return fmt.Errorf("no files matching %q", o.Filename)
    }

    service, err := o.StorageManager()
    if err != nil {
        return
    }
    storage := cloud.NewStorage(service, o.Spinner.Writer)

    wg, ctx := errgroup.WithContext(o.Context)
    semaphore := make(chan struct{}, runtime.NumCPU()-1)
    var outputLock sync.Mutex
    var outputs []string
    for _, target := range filenames {

        select {
        case <-ctx.Done():
            return ctx.Err()

        case semaphore <- struct{}{}:
            func(target string) {
                wg.Go(func() (err error) {
                    defer func() { <-semaphore }()

                    var output string
                    if o.StoredName == "" {
                        output = filepath.Base(target)
                    } else if len(filenames) == 1 {
                        output = o.StoredName
                    } else {
                        output = path.Join(o.StoredName, filepath.Base(target))
                    }

                    var loc *url.URL
                    loc, err = createURL(script.DataPrefix, output)
                    if err != nil {
                        return
                    }
                    err = storage.UploadFile(ctx, loc, target)
                    if err != nil {
                        return
                    }

                    outputLock.Lock()
                    defer outputLock.Unlock()
                    outputs = append(outputs, fmt.Sprintf("%v -> %v", target, loc))
                    return

                })
            }(target)

        }
    }

    err = wg.Wait()
    if err != nil {
        return
    }
    for _, v := range outputs {
        fmt.Fprintln(o.Stdout, v)
    }
    return

}

// CmdDataPut uploads a given file.
func CmdDataPut(c *cli.Context) (err error) {

    // Checn the number of arguments.
    n := c.NArg()
    if n < 1 || n > 2 {
        fmt.Printf("expected 1 or 2 arguments. (%d given)\n", n)
        return cli.ShowSubcommandHelp(c)
    }

    // Get metadata.
    m, err := getMetadata(c)
    if err != nil {
        return
    }

    // Set up options.
    opt := &optDataPut{
        Metadata:   m,
        Filename:   c.Args().First(),
        StoredName: filepath.ToSlash(c.Args().Get(1)),
    }

    // Execute the command.
    if err := opt.run(); err != nil {
        err = cli.NewExitError(err.Error(), 2)
    }
    return

}