App-dropboxapi

 view release on metacpan or  search on metacpan

script/dropbox-api  view on Meta::CPAN

#!/usr/bin/env perl

use strict;
use warnings;
use Cwd 'abs_path';
use DateTime;
use DateTime::Format::Strptime;
use Encode;
use Encode::Locale;
use File::Basename qw(dirname basename);
use File::Spec::Functions qw(abs2rel catfile);
use File::Temp;
use Getopt::Std;
use JSON;
use Path::Class;
use POSIX qw();
use WebService::Dropbox 2.06;

our $VERSION = '2.13';

my $limit = 10 * 1024 * 1024; # files_put_chunked method has large file support.

if ($^O eq 'darwin') {
    require Encode::UTF8Mac;
    $Encode::Locale::ENCODING_LOCALE_FS = 'utf-8-mac';
}

binmode STDOUT, ':utf8';
binmode STDERR, ':utf8';

my $config_file = file( $ENV{DROPBOX_CONF} || ($ENV{HOME} || $ENV{HOMEPATH}, '.dropbox-api-config') );

my $command = shift || '';
my @args;
for (@{ [ @ARGV ] }) {
    last if $_ =~ qr{ \A - }xms;
    push @args, shift;
}

my %opts;
if ($command eq 'du') {
    getopts('vDhed:', \%opts);
} else {
    getopts('ndvDshePp:', \%opts);
}

push @args, @ARGV;

my $dry       = $opts{n};
my $delete    = $opts{d};
my $verbose   = $opts{v};
my $debug     = $opts{D};
my $human     = $opts{h};
my $printf    = $opts{p};
my $public    = $opts{P};
my $env_proxy = $opts{e};
my $max_depth = $opts{d};

if ($opts{s}) {
    die "-s is gone.";
}

if ($command eq '-v') {
    &help('version');
    exit(0);
}

if ($command eq 'setup' || !-f $config_file) {
    &setup();
}

# connect dropbox
my $config = decode_json($config_file->slurp);
$config->{key} or die 'please set config key.';
$config->{secret} or die 'please set config secret.';
$config->{access_token} or die 'please set config access_token.';
if ($config->{access_secret}) {
    warn "Auto migration OAuth1 Token to OAuth2 token...";
    my $oauth2_access_token = &token_from_oauth1($config->{key}, $config->{secret}, $config->{access_token}, $config->{access_secret});
    if ($oauth2_access_token) {
        delete $config->{access_secret};
        $config->{access_token} = $oauth2_access_token;
        $config_file->openw->print(encode_json($config));
        warn "=> Suucess.";
    } else {
        die "please setup.";
    }
}
if (my $access_level = delete $config->{access_level}) {
    if ($access_level eq 'a') {
        print "sandbox is gone, Are you sure you want to delete from the config the access_level? [y/n]: ";
        chomp( my $y = <STDIN> );
        if ($y =~ qr{ [yY] }xms) {
            delete $config->{access_level};
            $config_file->openw->print(encode_json($config));
            warn "=> Suucess.";
        } else {
            die "cancelled.";
        }
    }
}

$ENV{HTTP_PROXY} = $ENV{http_proxy} if !$ENV{HTTP_PROXY} && $ENV{http_proxy};
$ENV{NO_PROXY} = $ENV{no_proxy} if !$ENV{NO_PROXY} && $ENV{no_proxy};

my $box = WebService::Dropbox->new($config);
$box->env_proxy if $env_proxy;

my $strp = new DateTime::Format::Strptime( pattern => '%Y-%m-%dT%T' );
my $strpz = new DateTime::Format::Strptime( pattern => '%Y-%m-%dT%TZ' );

my $format = {
    i => 'id',
    n => 'name',
    b => 'size',
    e => 'thumb_exists', # jpg, jpeg, png, tiff, tif, gif or bmp
    d => 'is_dir',       # Check if .tag = "folder"
    p => 'path_display',
    P => 'path_lower',
    s => 'format_size',
    t => 'server_modified',
    c => 'client_modified', # For files, this is the modification time set by the desktop client when the file was added to Dropbox.
    r => 'rev', # A unique identifier for the current revision of a file. This field is the same rev as elsewhere in the API and can be used to detect changes and avoid conflicts.
    R => 'rev',
};

# ProgressBar
my $cols = 50;
if ($verbose) {
    eval {
        my $stty = `stty -a 2>/dev/null`;
        if ($stty =~ m|columns (\d+)| || $stty =~ m|(\d+) columns|) {
            $cols = $1;
        }
    };
}

my $exit_code = 0;

if ($command eq 'ls' or $command eq 'list') {
    &list(@args);
} elsif ($command eq 'find') {
    &find(@args);
} elsif ($command eq 'du') {
    &du(@args);
} elsif ($command eq 'copy' or $command eq 'cp') {
    &copy(@args);
} elsif ($command eq 'move' or $command eq 'mv') {
    &move(@args);
} elsif ($command eq 'mkdir' or $command eq 'mkpath') {
    &mkdir(@args);
} elsif ($command eq 'delete' or $command eq 'rm' or $command eq 'rmtree') {
    &delete(@args);
} elsif ($command eq 'upload' or $command eq 'up' or $command eq 'put') {
    &upload(@args);
} elsif ($command eq 'download' or $command eq 'dl' or $command eq 'get') {
    &download(@args);
} elsif ($command eq 'sync') {
    &sync(@args);
} elsif ($command eq 'help' or (not length $command)) {
    &help(@args);
} else {
    die "unknown command $command";
}

exit($exit_code);

sub help {
    my ($command) = @_;

    $command ||= '';

    my $help;
    if ($command eq 'ls' or $command eq 'list') {
        $help = q{
        Name
            dropbox-api-ls - list directory contents

        SYNOPSIS
            dropbox-api ls <dropbox_path> [options]

        Example
            dropbox-api ls /Public
            dropbox-api ls /Public -h
            dropbox-api ls /Public -p "%d\t%s\t%TY/%Tm/%Td %TH:%TM:%TS\t%p\n"

        Options
            -h print sizes in human readable format (e.g., 1K 234M 2G)
            -p print format.
                %d ... is_dir ( d: dir, -: file )
                %i ... id
                %n ... name
                %p ... path_display
                %P ... path_lower
                %b ... bytes
                %s ... size (e.g., 1K 234M 2G)
                %t ... server_modified
                %c ... client_modified
                %r ... rev
                %Tk ... DateTime 'strftime' function (server_modified)
                %Ck ... DateTime 'strftime' function (client_modified)
        };
    } elsif ($command eq 'find') {
        $help = q{
        Name
            dropbox-api-find - walk a file hierarchy

        SYNOPSIS
            dropbox-api find <dropbox_path> [options]

        Example
            dropbox-api find /Public
            dropbox-api find /Public -h
            dropbox-api find /Public -p "%d\t%s\t%TY/%Tm/%Td %TH:%TM:%TS\t%p\n"

        Options
            -h print sizes in human readable format (e.g., 1K 234M 2G)
            -p print format.
                %d ... is_dir ( d: dir, -: file )
                %i ... id
                %n ... name
                %p ... path_display
                %P ... path_lower
                %b ... bytes
                %s ... size (e.g., 1K 234M 2G)
                %t ... server_modified
                %c ... client_modified
                %r ... rev
                %Tk ... DateTime 'strftime' function (server_modified)
                %Ck ... DateTime 'strftime' function (client_modified)
        };
    } elsif ($command eq 'du') {
        $help = q{
        Name
            dropbox-api-du - list directory contents

        SYNOPSIS
            dropbox-api du <dropbox_path> [options]

        Example
            dropbox-api du /Public
            dropbox-api du / -h
            dropbox-api du / -d 1

        Options
            -h print sizes in human readable format (e.g., 1K 234M 2G)
            -d depth.
        };
    } elsif ($command eq 'copy' or $command eq 'cp') {
        $help = q{
        Name
            dropbox-api-cp - copy file or directory

        SYNOPSIS
            dropbox-api cp <source_file> <target_file>

        Example
            dropbox-api cp /Public/hoge.txt /Public/foo.txt
            dropbox-api cp /Public/work /Public/work_bak
        };
    } elsif ($command eq 'move' or $command eq 'mv') {
        $help = q{
        Name
            dropbox-api-mv - move file or directory

        SYNOPSIS
            dropbox-api mv <source_file> <target_file>

        Example
            dropbox-api mv /Public/hoge.txt /Public/foo.txt
            dropbox-api mv /Public/work /Public/work_bak
        };
    } elsif ($command eq 'mkdir' or $command eq 'mkpath') {
        $help = q{
        Name
            dropbox-api-mkdir - make directory (Create intermediate directories as required)

        SYNOPSIS
            dropbox-api mkdir <directory>

        Example
            dropbox-api mkdir /Public/product/chrome-extentions/foo
        };
    } elsif ($command eq 'delete' or $command eq 'rm' or $command eq 'rmtree') {
        $help = q{
        Name
            dropbox-api-rm - remove file or directory (Attempt to remove the file hierarchy rooted in each file argument)

        SYNOPSIS
            dropbox-api rm <file_or_directory>

script/dropbox-api  view on Meta::CPAN

        }
    }
    if (!$remote_base) {
        my $size = $dir_map->{'/'} || 0;
        if ($human) {
            $size = format_bytes($size);
        }
        printf("%s\t%s\n", $size, '/');
    }
    for my $content (@{ $entries }) {
        if ($content->{'.tag'} ne 'folder') {
            next;
        }
        if (defined $max_depth) {
            my $path = $content->{path_lower};
            $path =~ s|^\Q$remote_base\E/?||i;
            my $depth = $path ? scalar(split('/', $path)) : 0;
            if ($depth > $max_depth) {
                next;
            }
        }
        my $size = $dir_map->{ lc $content->{path_lower} } || 0;
        if ($human) {
            $size = format_bytes($size);
        }
        printf("%s\t%s\n", $size, $content->{path_display});
    }
}

sub _paths ($$) {
    my ($base_path, $path) = @_;
    $path =~ s|^\Q$base_path\E/?||i;
    my @paths;
    my $dir = $base_path || '/';
    push @paths, $dir;
    my @names = split '/', $path;
    pop @names;
    for my $name (@names) {
        if ($dir ne '/') {
            $dir .= '/';
        }
        $dir .= $name;
        push @paths, $dir;
    }
    return @paths;
}

sub list {
    my $remote_base = decode('locale_fs', slash(shift));
    if ($remote_base eq '/') {
        $remote_base = '';
    }
    my $list = $box->list_folder($remote_base) or die $box->error;
    for my $entry (@{ $list->{entries} }) {
        print &_line($entry);
    }
}

sub _line {
    my ($content) = @_;
    $strp ||= new DateTime::Format::Strptime( pattern => '%Y-%m-%dT%T' );
    my $dt;
    my $ct;
    my $get = sub {
        my $key = $format->{ $_[0] };
        if ($key eq 'format_size') {
            return exists $content->{size} ? format_bytes($content->{size}) : '   -';
        } elsif ($key eq 'is_dir') {
            $content->{'.tag'} eq 'folder' ? 'd' : '-';
        } elsif ($key eq 'thumb_exists') {
            if ($content->{path_display} =~ qr{ \.(?:jpg|jpeg|png|tiff|tif|gif|bmp) \z }xms && $content->{size} < 20 * 1024 * 1024) {
                return 'true';
            } else {
                return 'false';
            }
        } else {
            return exists $content->{ $key } ? $content->{ $key } : '-';
        }
    };
    if ($printf) {
        my $line = eval qq{"$printf"};
        if ($content->{server_modified}) {
            $line=~s/\%T([^\%])/
                $dt ||= $strpz->parse_datetime($content->{server_modified});
                $dt->strftime('%'.$1);
            /egx;
        } else {
            $line=~s/\%TY/----/g;
            $line=~s/\%T([^\%])/--/g;
        }
        if ($content->{client_modified}) {
            $line=~s/\%C([^\%])/
                $ct ||= $strpz->parse_datetime($content->{client_modified});
                $ct->strftime('%'.$1);
            /egx;
        } else {
            $line=~s/\%CY/----/g;
            $line=~s/\%C([^\%])/--/g;
        }
        $line=~s|\%([^\%])|$get->($1)|eg;
        return $line;
    } else {
        return sprintf "%s %8s %s %s\n",
            ($content->{'.tag'} eq 'folder' ? 'd' : '-'),
            $get->($human ? 's' : 'b'),
            $get->('t'),
            $content->{path_display};
    }
}

sub find {
    my $remote_base = decode('locale_fs', slash(shift));
    if ($remote_base eq '/') {
        $remote_base = '';
    }
    $printf ||= "%p\n";
    my $entries = _find($remote_base);
    for my $entry (@{ $entries }) {
        print &_line($entry);
    }
}

script/dropbox-api  view on Meta::CPAN

        printf "skip %s\n", $content->{path_display} if $verbose;
    }
}

sub sync_upload {
    my ($remote_base, $local_base) = @_;


    if ($verbose) {
        print "remote_base: $remote_base\n";
        print "local_base: $local_base\n";
    }

    print "** upload **\n" if $verbose;

    my $remote_map = {};
    my $remote_path_map = {};

    my $entries = _find($remote_base);
    for my $content (@{ $entries }) {
        my $remote_path = $content->{path_display};
        my $rel_path = remote_abs2rel($remote_path, $remote_base);
        unless (length $rel_path) {
            next;
        }
        $remote_map->{ lc $rel_path } = $content;
        $remote_path_map->{ $content->{path_display} } = $content;
        if ($debug) {
            printf "find: %s\n", $rel_path;
        }
    }

    my @makedirs;
    $local_base->recurse(
        preorder => 0,
        depthfirst => 1,
        callback => sub {
            my $local_path = shift;
            if ($local_path eq $local_base) {
                return;
            }
            my $rel_path = decode('locale_fs', abs2rel($local_path, $local_base));
            my $remote_path = file($remote_base, $rel_path);
            my $content = delete $remote_map->{ lc $rel_path };

            # exists file or directory
            if ($content) {
                delete $remote_path_map->{ $content->{path_display} };

                unless (-f $local_path) {
                    return;
                }

                if (has_change($local_path, $content)) {
                    printf "upload %s %s\n", $rel_path, $remote_path;
                    unless ($dry) {
                        if ($content->{size} == -s $local_path) {
                            $box->delete("$remote_path");
                        }
                        my $local_epoch = $local_path->stat->mtime;
                        &put($local_path, "$remote_path", { client_modified => $strp->format_datetime(DateTime->from_epoch( epoch => $local_epoch )) . 'Z' }) or die $box->error;
                    }
                    push @makedirs, $rel_path;
                } elsif ($verbose) {
                    printf "skip %s\n", $rel_path;
                }
            }

            # new file
            elsif (-f $local_path) {
                unless ($dry) {
                    my $local_epoch = $local_path->stat->mtime;
                    &put($local_path, "$remote_path", { client_modified => $strp->format_datetime(DateTime->from_epoch( epoch => $local_epoch )) . 'Z' });
                }
                if (!$dry && $box->error) {
                    warn "upload failure $rel_path $remote_path (" . $box->error . ")";
                } else {
                    printf "upload %s %s\n", $rel_path, $remote_path;
                    push @makedirs, $rel_path;
                }
            }

            # new directory
            elsif (-d $local_path) {

                if (grep { $_ =~ qr{ \A\Q$rel_path }xms } @makedirs) {
                    return;
                }

                printf "mktree %s %s\n", $rel_path, $remote_path;

                unless ($dry) {
                    $box->create_folder("$remote_path") or die $box->error;
                }

                push @makedirs, $rel_path;
            } else {
                printf "unknown %s\n", $rel_path;
            }
        }
    );

    return unless $delete;

    print "** delete **\n" if $verbose;

    my @deletes;
    for my $content_path ( keys %$remote_path_map ) {

        if (chomp_slash($content_path) eq chomp_slash($remote_base)) {
            next;
        }

        if (grep { $content_path =~ qr{ \A\Q$_ }xms } @deletes) {
            next;
        }

        unless ($dry) {
            $box->delete($content_path) or die $box->error;
        }

        push @deletes, $content_path;

        printf "delete %s\n", remote_abs2rel($content_path, $remote_base);
    }
}

sub sync_upload_file {
    my ($remote_base, $local_path) = @_;

    if ($verbose) {
        print "remote_base: $remote_base\n";
        print "local_path: $local_path\n";
    }

    my $remote_path;
    my $content;
    {
        local $SIG{__WARN__} = sub {};

        $content = $box->get_metadata(chomp_slash($remote_base));

        # exists folder
        if ($content && $content->{'.tag'} eq 'folder') {
            $remote_path = file($remote_base, basename($local_path));
            my $remote_file_content = $box->get_metadata(chomp_slash("$remote_path"));
            if ($remote_file_content) {
                if ($remote_file_content->{'.tag'} eq 'folder') {
                    die "$remote_path is folder.";
                }
                $content = $remote_file_content;
            }
        } else {
            if ($remote_base =~ qr{ / \z }xms) {
                $remote_path = file($remote_base, basename($local_path));
            } else {
                $remote_path = $remote_base;
            }
        }
    }

    # exists file
    if ($content && $content->{'.tag'} ne 'folder') {
        if ($debug) {
            printf "find: %s\n", $content->{path_display};
        }
        $remote_path = $content->{path_display};

        if (has_change($local_path, $content)) {
            printf "upload %s %s\n", $local_path, $remote_path;
            unless ($dry) {
                if ($content->{size} == -s $local_path) {
                    $box->delete("$remote_path");
                }
                my $local_epoch = $local_path->stat->mtime;
                &put($local_path, "$remote_path", { client_modified => $strp->format_datetime(DateTime->from_epoch( epoch => $local_epoch )) . 'Z' }) or die $box->error;
            }
        } elsif ($verbose) {
            printf "skip %s\n", $local_path;
        }
        return;
    }

    unless ($dry) {
        my $local_epoch = $local_path->stat->mtime;
        &put($local_path, "$remote_path", { client_modified => $strp->format_datetime(DateTime->from_epoch( epoch => $local_epoch )) . 'Z' });
    }

    printf "upload %s %s\n", $local_path, $remote_path;
}

sub has_change ($$) {
    my ($local_path, $content) = @_;

    my $remote_epoch = $strpz->parse_datetime($content->{client_modified})->epoch;
    my $local_epoch = $local_path->stat->mtime;
    my $remote_size = $content->{size};
    my $local_size = $local_path->stat->size;

    if ($debug) {
        printf "remote: %10s %10s %s\n", $remote_epoch, $remote_size, $content->{path_display};
        printf "local:  %10s %10s %s\n", $local_epoch, $local_size, decode('locale_fs', $local_path);
    }

    if (($remote_size != $local_size) || ($remote_epoch != $local_epoch)) {
        return 1;
    }

    return;
}

sub put {
    my ($file, $path, $optional_params) = @_;

    my $commit_params = {
        path => "$path",
        mode => 'overwrite',
        %{ $optional_params || +{} },
    };

    my $content = $file->openr;
    my $size = -s $file;
    my $threshold = 10 * 1024 * 1024;

    if ($size < $threshold) {
        return $box->upload("$path", $content, $commit_params);
    }

    my $session_id;
    my $offset = 0;

    my $limit = 4 * 1024 * 1024;

    $| = 1;

    my $upload;
    $upload = sub {
        my $buf;
        my $total = 0;
        my $chunk = 1024;
        my $tmp = File::Temp->new;
        my $is_last;
        while (my $read = read($content, $buf, $chunk)) {
            $tmp->print($buf);
            $total += $read;
            my $remaining = $limit - $total;



( run in 1.202 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )