use strict;
use Carp;
sub new{
my ($class, %options) = @_;
croak "Local folder '$options{local_root}' not found"
unless -d $options{local_root};
$options{local_root} .= '/'
unless $options{local_root} =~ m{/$};
my $gd = Net::Google::Drive::Simple->new();
$options{remote_root} = '/'.$options{remote_root}
unless $options{remote_root} =~ m{^/};
# XXX To support slashes in folder names in remote_root, I would have
# to implement a different remote_root lookup mechanism here:
my (undef, $remote_root_ID) = $gd->children( $options{remote_root});
my $self = {
remote_root_ID => $remote_root_ID,
export_format => ['opendocument', 'html'],
download_condition => \&_should_download,
force => undef, # XXX move this to mirror()
net_google_drive_simple => $gd,
%options
};
bless $self, $class;
}
sub mirror{
my $self = shift;
_process_folder(
$self,
$self->{remote_root_ID},
$self->{local_root}
);
}
sub _process_folder{
my ($self, $folder_id, $path) = @_;
my $gd = $self->{net_google_drive_simple};
my $children = $gd->children_by_folder_id($folder_id);
for my $child (@$children){
my $file_name = $child->title();
$file_name =~ s{/}{_};
my $local_file = $path.$file_name;
# a google document: export to preferred format
if ($child->can( "exportLinks" )){
next unless $self->{download_condition}
->($self, $child, $local_file);
print "$path$file_name ..exporting\n";
my $type;
FOUND:
foreach my $preferred_type (@{$self->{export_format}}){
foreach my $t (keys %{$child->exportLinks()}){
$type = $t;
last FOUND if $t =~ /$preferred_type/;
}
}
my $url = $child->exportLinks()->{$type};
$gd->download($url, $local_file);
next;
}
# pdfs and the like get downloaded directly
if ($child->can( "downloadUrl" )){
next unless $self->{download_condition}
->($self, $child, $local_file);
print "$path$file_name ..downloading\n";
$gd->download( $child, $local_file);
next;
}
# if we reach this, we could not "fetch" the file. A dir, then..
mkdir ($path.$file_name)
unless -d $path.$file_name;
_process_folder($self, $child->id(), $path.$file_name.'/');
}
}
sub _should_download{
my ($self, $remote_file, $local_file) = @_;
return 1 if $self->{force};
my $date_time_parser = DateTime::Format::RFC3339->new();
my $local_epoch = (stat($local_file))[9];
my $remote_epoch = $date_time_parser
->parse_datetime($remote_file->modifiedDate())
->epoch();
if (-f $local_file and $remote_epoch < $local_epoch ){
return 0;
}
else {
return 1;
}
}
1;
__END__
=head1 NAME
Net::Google::Drive::Simple::Mirror - Locally mirror a Google Drive folder structure
=head1 SYNOPSIS
use Net::Google::Drive::Simple::Mirror;
# requires a ~/.google-drive.yml file containing an access token,
# see documentation of Net::Google::Drive::Simple
my $google_docs = Net::Google::Drive::Simple::Mirror->new(
remote_root => '/folder/on/google/docs',
local_root => 'local/folder',
export_format => ['opendocument', 'html'],
);
$google_docs->mirror();
=head1 DESCRIPTION
Net::Google::Drive::Simple::Mirror allows you to locally mirror a folder structure from Google Drive.
=head2 GETTING STARTED
For setting up your access token see the documentation of Net::Google::Drive::Simple.
=head1 METHODS
=over 4
=item C<new()>
Creates a helper object to mirror a remote folder to a local folder.
Parameters:
remote_root: folder on your Google Docs account. See "CAVEATS" below.
local_root: local folder to put the mirrored files in.
export_format: anonymous array containing your preferred export formats.
Google Doc files may be exported to several formats. To get an idea of available formats, check 'exportLinks()' on a Google Drive Document or Spreadsheet, e.g.
my $gd = Net::Google::Drive::Simple->new(); # 'Simple' not 'Mirror'
my $children = $gd->children( '/path/to/folder/on/google/drive' );
for my $child ( @$children ) {
if ($child->can( 'exportLinks' )){
foreach my $type (keys %{$child->exportLinks()}){
print "$type";
}
}
}
Now, specify strings that your preferred types match against. The default is ['opendocument', 'html']
download_condition: reference to a sub that takes the remote file name and the local file name as parameters. Returns true or false. The standard implementation is:
sub _should_download{
my ($self, $remote_file, $local_file) = @_;
return 1 if $self->{force};
my $date_time_parser = DateTime::Format::RFC3339->new();
my $local_epoch = (stat($local_file))[9];
my $remote_epoch = $date_time_parser
->parse_datetime
($remote_file->modifiedDate())
->epoch();
if (-f $local_file and $remote_epoch < $local_epoch ){
return 0;
}
else {
return 1;
}
}
force: download all files and replace local copies.
=item C<mirror()>
Recursively mirrors Google Drive folder to local folder.
=back
=head1 CAVEATS
At the moment, remote_root must not contain slashes in the file names of its folders.
remote_root => 'Folder/Containing/Letters A/B'
is not existing because folder "Letters A/B" contains a slash:
Folder
`--Containing
`--Letters A/B
This will be resolved to:
Folder
`--Containing
`--Letters A
`--B
The remote_root 'Example/root' may contain folders and files with slashes. These get replaced with underscores in the local file system.
remote_root => 'Example/root';
Example
`--root
`--Letters A/B
With local_root 'Google-Docs-Mirror' this locally becomes:
local_root => 'Gooogle-Docs-Mirror';
Google-Docs-Mirror
`--Letters A_B
(Net::Google::Drive::Simple::Mirror uses folder ID's as soon as it has found the remote_root and does not depend on folder file names.)
=head1 AUTHOR
Matthias Bloch, <lt>matthias at puffin ch<gt>
=head1 COPYRIGHT AND LICENSE
=Copyright (C) 2014 by :m)
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.8 or,
at your option, any later version of Perl 5 you may have available.
=cut