Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

executable file 305 lines (265 sloc) 10.138 kb
#!/usr/bin/env perl -w
##############################################################################
# File : dropsync
# Author : Guillaume-Jean Herbiet <guillaume-jean@herbiet.net>
#
#
# Copyright (c) 2009 Guillaume-Jean Herbiet (http://herbiet.net)
#
# This program 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.
#
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
#
# Guillaume-Jean Herbiet
# <guillaume-jean@herbiet.net>
#
##############################################################################
use strict;
use warnings;
#-----------------------------------------------------------------------------
# Load aditional packages
#
use Getopt::Long; # To easily retrieve arguments from command-line
use Term::ANSIColor qw(:constants); # Colored output for the terminal
use YAML; # To parse the config file
use Data::Dumper; # Dump data structues (mainly used for debug)
use File::Basename; # To easily manipulate files and paths
use File::Copy "cp"; # Easily copy files while keeping permissions
use File::Path qw(make_path); # To easily create required path if necessary
use File::Compare; # To compare file content
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Log and debug
#
sub say { print @_, "\n"; }
sub ohai { say BOLD, BLUE, "==> ", RESET, @_;}
sub oops { say BOLD, YELLOW, "/!\\ ",RESET, @_;}
sub fail { say BOLD, RED, "!!! ", RESET, @_;}
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Global variables
#
my %OPTIONS; # Hash of passed options
my $OPTIONS_VALID; # Are the passed options valid ?
my @ARGS = @ARGV; # Array of passed options
my $COMMAND = `basename $0`; # Name of the script
chomp($COMMAND);
my $VERSION = "0.3"; # Version of this script
#
# Generic variables
#
my $VERBOSE = 0; # Verbose mode
my $DEBUG = 0; # Debug mode
my $UNSAFE = 0; # Run in unsafe mode
my $FORCED_PUSH = 0; # Force pushing to dropsync
my $FORCED_RETRIEVE = 0; # Force retrieve from dropsync
#
# Script specific variables
#
my $config_file = $ENV{'HOME'}."/.dropsyncrc.yaml"; # Default location of config file
my $config; # Hashref of the config options
my $dropbox_location = $ENV{'HOME'}."/Dropbox"; # DropBox folder location
my $dropsync_folder = "dropsync"; # dropsync folder name
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Get passed arguments and check for validity.
#
$OPTIONS_VALID = GetOptions(
\%OPTIONS,
'help|h' => sub { USAGE(); },
'version' => sub { VERSION_MESSAGE(); },
'verbose|v+' => \$VERBOSE,
'debug|d' => sub { $VERBOSE = 1; $DEBUG = 1; },
'config-file|f=s' => \$config_file,
'unsafe+' => \$UNSAFE,
'force-push|p+' => \$FORCED_PUSH,
'force-retrieve|r+' => \$FORCED_RETRIEVE
);
unless ($OPTIONS_VALID) {
fail "Error in arguments.";
USAGE(1);
}
sub USAGE {
my $exitval = defined($_) ? $_ : 0;
print <<EOF;
$COMMAND [-h|--help] [--version] [--verbose|-v] [--debug|-d]
[--config-file|-f <path to config file>] [--unsafe]
--help, -h : Print this help, then exit
--version : Print the script version, then exit
--verbose, -v : Enable verbose mode
--debug, -d : Enable debug mode
--config-file, -f : Alternate location for the configuration file
(Default: \$HOME/.dropsyncrc.yaml)
--force-push, -p : Force pushing local files to the dropsync folder,
whatever the conflict resolution status.
--force-retrieve, -r: Force retrieving local files from the dropsync folder,
whatever the conflict resolution status.
--unsafe : When run in unsafe mode, no backup of conflicting
files will be kept.
(This mode is not recommanded and disabled by default).
EOF
exit $exitval;
}
sub VERSION_MESSAGE {
say "This is ", BOLD, "$COMMAND", RESET, " v$VERSION.";
print <<EOF;
Copyright (c) 2010 Guillaume-Jean Herbiet (http://herbiet.net)
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
EOF
exit 0;
}
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Core code
#
#
# Read configuration from the specified file
$config = read_config_file($config_file);
#
#
# dropsync folder location management
#
my $src_folder = setup_src_folder();
#
# Files backup/update
#
ohai "Syncing selected files." if $VERBOSE;
for my $file (@{$config->{'files'}}) {
if (-f $ENV{'HOME'}."/".$file && ! -f $src_folder."/".$file) {
say CYAN, "$file", RESET, " requires to be put in dropsync." if $VERBOSE;
safe_copy($ENV{'HOME'}."/".$file, $src_folder."/".$file);
}
elsif ( -f $src_folder."/".$file && ! -f $ENV{'HOME'}."/".$file) {
say CYAN, "$file", RESET, " requires to be pulled from dropsync." if $VERBOSE;
safe_copy($src_folder."/".$file, $ENV{'HOME'}."/".$file);
}
elsif ( -f $src_folder."/".$file && -f $ENV{'HOME'}."/".$file) {
say CYAN, "$file", RESET, " exists in both your file system and in dropsync." if $VERBOSE;
fix_conflict($file);
}
elsif ( ! -f $src_folder."/".$file && ! -e $ENV{'HOME'}."/".$file) {
oops CYAN, "$file", RESET, " exits neither in your file system nor in dropsync and will be ignored.";
}
else {
oops CYAN, "$file", RESET, " did not match any sync case managed by dropsync and will be ignored.";
}
}
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Additional functions
#
sub read_config_file {
my $file = shift;
# Try to open the configuration file or die
unless (-f $file) {
fail "Configuration file $file does not exist!"; exit 2;
} elsif ($VERBOSE) { ohai "Reading configuration from $file."; }
# Read the configuration to a hash
# NOTE: config_array and config_string should be empty
my ($config_hash, $config_array, $config_string) = YAML::LoadFile($file);
ohai "Read configuration:\n", Dumper($config_hash, $config_array, $config_string) if $DEBUG;
oops "Some unnamed variables were found while reading config file $_. They will be ignored."
if (defined $config_array || defined $config_string);
return $config_hash;
}
sub setup_src_folder {
# Update the location of the Dropbox location
# and the dropsync folder if required
$dropbox_location = $config->{'dropbox_location'}
if (exists $config->{'dropbox_location'} && !$config->{'dropbox_location'} eq "");
$dropsync_folder = $config->{'dropsync_folder'}
if (exists $config->{'dropsync_folder'} && !$config->{'dropsync_folder'} eq "");
# Create the dropsync folder if required
my $src = $dropbox_location."/".$dropsync_folder;
unless (-d $src) {
mkdir $src;
ohai "Creating non-existing dropsync folder at $src."
if $VERBOSE;
}
say "Using ", YELLOW, "$src", RESET, " as source folder." if $VERBOSE;
return $src;
}
#
# File manipulation functions
#
sub fix_conflict {
my $filepath = shift;
# Read the last modification time, the newest wins
my $lastmod_fs = $FORCED_RETRIEVE ? 0 : (stat $ENV{'HOME'}."/".$filepath)[9];
my $lastmod_ds = $FORCED_PUSH ? 0 : (stat $src_folder."/".$filepath)[9];
if (compare($ENV{'HOME'}."/".$filepath, $src_folder."/".$filepath) == 0) {
say " - Files are identical, nothing will be changed." if $DEBUG;
}
elsif ($lastmod_fs > $lastmod_ds && cleanup_file($src_folder."/".$filepath)) {
say " - Updating file in dropsync using $ENV{'HOME'}/$filepath" if $DEBUG;
#move_to_dropsync_and_link($filepath);
safe_copy($ENV{'HOME'}."/".$filepath, $src_folder."/".$filepath);
}
elsif ($lastmod_fs < $lastmod_ds && cleanup_file($ENV{'HOME'}."/".$filepath)) {
say " - Updating file in file system using $src_folder/$filepath" if $DEBUG;
safe_copy($src_folder."/".$filepath, $ENV{'HOME'}."/".$filepath);
}
else {
oops "Conflict on $ENV{'HOME'}."/".$filepath was not resolved. This file will not updated.";
}
}
sub safe_copy {
my ($src, $dest) = @_;
unless (-f $src) {
oops " - Source file $src does not exist.";
return 0;
}
elsif ( -f $dest) {
oops " - Destination file $dest already exists";
return 0;
}
elsif ( create_path_if_required($dest) && cp $src, $dest) {
say " - Copy $src to $dest" if $DEBUG;
return 1;
}
else {
oops "Error in copying $src to $dest: $!";
return 0;
}
}
sub create_path_if_required {
my $filepath = shift;
my $path = dirname($filepath);
if (-d $path) {
say " - Required path $path already exists, no need to create" if $DEBUG;
return 1;
}
elsif (make_path($path)) {
say " - Created required path $path" if $DEBUG;
return 1;
}
else {
oops "Error in creating required path $path: $!";
return 0;
}
}
sub cleanup_file {
my $path = shift;
if ($UNSAFE && !unlink $path) {
oops "Error in deleting old version of $path: $!";
oops "Further fixing will be ignored.";
return 0;
}
elsif (!$UNSAFE && !rename $path, $path.".ds_".time) {
oops "Error in saving old version of $path: $!";
oops "Further fixing will be ignored.";
return 0;
}
return 1;
}
#-----------------------------------------------------------------------------
Jump to Line
Something went wrong with that request. Please try again.