App-dropboxapi

 view release on metacpan or  search on metacpan

script/dropbox-api  view on Meta::CPAN

            find  walk a file hierarchy
            du    disk usage statistics
            cp    copy file or directory
            mv    move file or directory
            mkdir make directory (Create intermediate directories as required)
            rm    remove file or directory (Attempt to remove the file hierarchy rooted in each file argument)
            put   upload file
            get   download file
            sync  sync directory (local => dropbox or dropbox => local)

        Common Options
            -e enable env_proxy ( HTTP_PROXY, NO_PROXY )
            -D enable debug
            -v verbose

        See 'dropbox-api help <command>' for more information on a specific command.
        };
    }
    $help =~ s|^ {8}||mg;
    $help =~ s|^\s*\n||;
    print "\n$help\n";
}

sub setup {
    my $config = {};

    print "Please Input API Key: ";
    chomp( my $key = <STDIN> );
    die 'Get API Key from https://www.dropbox.com/developers' unless $key;
    $config->{key} = $key;

    print "Please Input API Secret: ";
    chomp( my $secret = <STDIN> );
    die 'Get API Secret from https://www.dropbox.com/developers' unless $secret;
    $config->{secret} = $secret;

    my $box = WebService::Dropbox->new($config);
    $box->env_proxy if $env_proxy;
    my $login_link = $box->authorize;
    die $box->error if $box->error;
    print "1. Open the Login URL: $login_link\n";
    print "2. Input code and press Enter: ";
    chomp( my $code = <STDIN> );
    unless ($box->token($code)) {
        die $box->error;
    }

    $config->{access_token} = $box->access_token;
    print "success! try\n";
    print "> dropbox-api ls\n";
    print "> dropbox-api find /\n";

    $config_file->openw->print(encode_json($config));

    chmod 0600, $config_file;

    exit(0);
}

sub du {
    my $remote_base = decode('locale_fs', slash(shift));
    $remote_base =~ s|/$||;
    my $entries = _find($remote_base);
    my $dir_map = {};
    for my $content (@{ $entries }) {
        if ($content->{'.tag'} eq 'folder') {
            next;
        }
        my @paths = _paths($remote_base, $content->{path_lower});
        for my $path (@paths) {
            $dir_map->{ lc $path } ||= 0;
            $dir_map->{ lc $path } += $content->{size};
        }
    }
    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);
    }
}

sub _find ($) {
    my $remote_base = decode('locale_fs', slash(shift));
    if ($remote_base eq '/') {
        $remote_base = '';
    }
    my @entries;
    my $fetch;
    my $count = 0;
    $fetch = sub {
        my $cursor = shift;
        my $list;
        if ($cursor) {
            $list = $box->list_folder_continue($cursor) or die $box->error;
        } else {
            $list = $box->list_folder($remote_base, {
                recursive => JSON::true,
            }) or die $box->error;
        }
        push @entries, @{ $list->{entries} };
        if ($list->{has_more}) {
            if ($verbose) {
                $| = 1;
                $count++;
                printf("\r" . (('.') x $count));
            }
            $fetch->($list->{cursor});
        }
    };
    $fetch->();
    if ($verbose) {
        print "\n";
    }
    [ sort { $a->{path_lower} cmp $b->{path_lower} } @entries ];
}

sub copy {
    my ($src, $dst) = @_;
    my $res = $box->copy(decode('locale_fs', slash($src)), decode('locale_fs', slash($dst))) or die $box->error;
    print pretty($res) if $verbose;
}

sub move {
    my ($src, $dst) = @_;
    my $res = $box->move(decode('locale_fs', slash($src)), decode('locale_fs', slash($dst))) or die $box->error;
    print pretty($res) if $verbose;
}

sub mkdir {
    my ($dir) = @_;
    my $res = $box->create_folder(decode('locale_fs', slash($dir))) or die $box->error;
    print pretty($res) if $verbose;
}

sub delete {
    my ($file_or_dir) = @_;
    my $res = $box->delete(decode('locale_fs', slash($file_or_dir))) or die $box->error;
    print pretty($res) if $verbose;
}

sub upload {
    my ($file, $path) = @_;
    $path =~ s|^dropbox:/|/|
        or die "Usage: \n    dropbox-api upload /tmp/local.txt dropbox:/Public/some.txt";
    my $local_path = file($file);
    if ((! length $path) or $path =~ m|/$|) {
        $path.= basename($file);
    }
    my $res = &put($local_path, decode('locale_fs', $path)) or die $box->error;

    if ($verbose) {
        print pretty($res);
    }

    my $id = $res->{id};

    if ($public) {
        my $list_shared_links = $box->api({
            url => 'https://api.dropboxapi.com/2/sharing/list_shared_links',
            params => {
                path => $id,
            }
        }) or die $box->error;
        for (@{ $list_shared_links->{links} }) {
            if ($id eq $_->{id} && $_->{link_permissions}{resolved_visibility}{'.tag'} eq 'public') {
                print $_->{url}, "\n";
                return;
            }
        }

        my $res = $box->api({
            url => 'https://api.dropboxapi.com/2/sharing/create_shared_link_with_settings',
            params => {
                path => $path,
                settings => {
                    requested_visibility => 'public',
                }
            }
        }) or die $box->error;
        print $res->{url}, "\n";
    }
}

sub download {
    my ($path, $file) = @_;
    $path =~ s|^dropbox:/|/|
        or die "Usage: \n    dropbox-api download dropbox:/Public/some.txt /tmp/local.txt";
    my $fh = file($file)->openw or die $!;
    $box->download(decode('locale_fs', $path), $fh) or die $box->error;
    $fh->close;
}

sub sync {
    my ($arg1, $arg2) = @_;

    if ($dry) {
        print "!! enable dry run !!\n";
    }

    # download
    if ($arg1 =~ qr{ \A dropbox: }xms and $arg2 !~ qr{ \A dropbox: }xms) {

        my ($remote_base, $local_base) = ($arg1, $arg2);
        $remote_base = decode('locale_fs', $remote_base);
        $remote_base =~ s|^dropbox:||;

        if ($remote_base eq '/' || $remote_base eq '') {
            unless (-d $local_base) {
                die "missing $local_base";
            }
            &sync_download('/', dir(abs_path($local_base)));
        } else {
            my $content = $box->get_metadata(chomp_slash($remote_base)) or die $box->error;
            if ($content->{'.tag'} eq 'folder') {
                unless (-d $local_base) {
                    die "missing $local_base";
                }
                &sync_download($content->{path_display}, dir(abs_path($local_base)));
            } else {
                $local_base = -d $local_base ? dir(abs_path($local_base)) : -f $local_base ? file(abs_path($local_base)) : file($local_base);
                &sync_download_file($content, file($local_base));
            }
        }
    }

    # upload
    elsif ($arg1 !~ qr{ \A dropbox: }xms and $arg2 =~ qr{ \A dropbox: }xms) {

        my ($local_base, $remote_base) = ($arg1, $arg2);
        $remote_base = decode('locale_fs', $remote_base);
        $remote_base =~ s|^dropbox:||;

        if (-d $local_base) {
            &sync_upload($remote_base, dir(abs_path($local_base)));
        } elsif (-f $local_base) {
            &sync_upload_file($remote_base, file(abs_path($local_base)));
        } else {
            die "missing $local_base";
        }
    }

    # invalid command
    else {
        die "Usage: \n    dropbox-api sync dropbox:/Public/ /tmp/pub/\n" .
                   "or    dropbox-api sync /tmp/pub/ dropbox:/Public/";
    }
}

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

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

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

    my $entries = _find($remote_base);
    unless (@{ $entries }) {
        return;
    }

    my $remote_map = {};
    my $remote_inode_map = {};

    for my $content (@{ $entries }) {
        my $remote_path = $content->{path_display};
        my $rel_path = remote_abs2rel($remote_path, $remote_base);
        unless (length $rel_path) {
            if ($content->{'.tag'} eq 'folder') {
                next;
            } else {
                $rel_path = $content->{name};
            }
        }
        my $rel_path_enc = encode('locale_fs', $rel_path);
        $remote_map->{$rel_path}++;
        printf "check: %s\n", $rel_path if $debug;
        my $is_dir = $content->{'.tag'} eq 'folder' ? 1 : 0;
        my $local_path = $is_dir ? dir($local_base, $rel_path_enc) : file($local_base, $rel_path_enc);
        if ($is_dir) {
            printf "remote: %s\n", $remote_path if $debug;
            printf "local:  %s\n", $local_path if $debug;
            if (!-d $local_path) {
                $local_path->mkpath unless $dry;
                printf "mkpath %s\n", decode('locale_fs', $local_path);
            } else {
                printf "skip %s\n", $rel_path if $verbose;
            }
        } else {

            if ((!-f $local_path) || has_change($local_path, $content)) {

                if ($dry) {
                    printf "download %s\n", decode('locale_fs', $local_path);
                    next;
                }

                # not displayed in the dry-run for the insurance
                unless (-d $local_path->dir) {
                    printf "mkpath %s\n", decode('locale_fs', $local_path->dir);
                    $local_path->dir->mkpath;
                }

                my $local_path_tmp = $local_path . '.dropbox-api.tmp';
                my $fh;
                unless (open($fh, '>', $local_path_tmp)) {
                    warn "open failure " . decode('locale_fs', $local_path) . " (" . $! . ")";
                    $exit_code = 1;
                    next;
                }
                if ($box->download($content->{path_display}, $fh)) {
                    printf "download %s\n", decode('locale_fs', $local_path);
                    close($fh);
                    my $remote_epoch = $strpz->parse_datetime($content->{client_modified})->epoch;
                    unless (utime($remote_epoch, $remote_epoch, $local_path_tmp)) {
                        warn "set modification time failure " .  decode('locale_fs', $local_path);
                        $exit_code = 1;
                    }
                    unless (rename($local_path_tmp, $local_path)) {
                        unlink($local_path_tmp);
                        warn "rename failure " . decode('locale_fs', $local_path_tmp);
                        $exit_code = 1;
                    }
                } else {
                    unlink($local_path_tmp);
                    chomp( my $error = $box->error );
                    warn "download failure " . decode('locale_fs', $local_path) . " (" . $error . ")";
                    $exit_code = 1;
                }
            } else {
                printf "skip %s\n", $rel_path if $verbose;
            }
        }
        $remote_inode_map->{ &inode($local_path) } = $content;
    }

    if ($exit_code) {
        return;
    }

    unless ($delete) {
        return;
    }

    if ($verbose) {
        print "** delete **\n";
    }

    my @deletes;
    $local_base->recurse(
        preorder => 0,
        depthfirst => 1,
        callback => sub {
            my $local_path = shift;
            if ($local_path eq $local_base) {
                return;
            }

            my $rel_path_enc = abs2rel($local_path, $local_base);
            my $rel_path = decode('locale_fs', $rel_path_enc);

            if (exists $remote_map->{$rel_path}) {
                if ($verbose) {
                    printf "skip %s\n", $rel_path;
                }
            } elsif (my $content = $remote_inode_map->{ &inode($local_path) }) {
                my $remote_path = $content->{path_display};
                my $rel_path_remote = remote_abs2rel($remote_path, $remote_base);
                if ($verbose) {
                    if ($debug) {
                        printf "skip %s ( is %s )\n", $rel_path, $rel_path_remote;
                    } else {
                        printf "skip %s\n", $rel_path;
                    }
                }
            } elsif (-f $local_path) {
                printf "remove %s\n", $rel_path;
                push @deletes, $local_path;
            } elsif (-d $local_path) {
                printf "rmtree %s\n", $rel_path;
                push @deletes, $local_path;
            }
        }
    );

    if ($dry) {
        return;
    }

    for my $local_path (@deletes) {
        if (-f $local_path) {
            $local_path->remove;
        } elsif (-d $local_path) {
            $local_path->rmtree;
        }
    }
}

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

    if ($verbose) {
        print "remote_base: " . $content->{name} . "\n";
        print "local_base: $local_path\n";
    }

    if (-d $local_path) {
        $local_path = file($local_path, $content->{name});
    }

    if ((!-f $local_path) || has_change($local_path, $content)) {

        if ($dry) {
            printf "download %s\n", decode('locale_fs', $local_path);
            return;
        }

        unless (-d $local_path->dir) {
            printf "mkpath %s\n", decode('locale_fs', $local_path->dir);
            $local_path->dir->mkpath;
        }

        my $local_path_tmp = $local_path . '.dropbox-api.tmp';
        my $fh;
        unless (open($fh, '>', $local_path_tmp)) {
            warn "open failure " . decode('locale_fs', $local_path) . " (" . $! . ")";
            $exit_code = 1;
            return;
        }
        if ($box->download($content->{path_display}, $fh)) {
            printf "download %s\n", decode('locale_fs', $local_path);
            close($fh);
            my $remote_epoch = $strpz->parse_datetime($content->{client_modified})->epoch;
            unless (utime($remote_epoch, $remote_epoch, $local_path_tmp)) {
                warn "set modification time failure " .  decode('locale_fs', $local_path);
                $exit_code = 1;
            }
            unless (rename($local_path_tmp, $local_path)) {
                unlink($local_path_tmp);
                warn "rename failure " . decode('locale_fs', $local_path_tmp);
                $exit_code = 1;
            }
        } else {
            unlink($local_path_tmp);
            chomp( my $error = $box->error );
            warn "download failure " . decode('locale_fs', $local_path) . " (" . $error . ")";
            $exit_code = 1;
        }
    } else {
        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;
            }
        }
    );

script/dropbox-api  view on Meta::CPAN


        # 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;
            if ($chunk > $remaining) {
                $chunk = $remaining;
            }
            unless ($chunk) {
                last;
            }
        }

        $tmp->flush;
        $tmp->seek(0, 0);

        # finish or small file
        if ($total < $limit) {
            if ($session_id) {
                my $params = {
                    cursor => {



( run in 0.679 second using v1.01-cache-2.11-cpan-ceb78f64989 )