App-SimplenoteSync
view release on metacpan or search on metacpan
lib/App/SimplenoteSync.pm view on Meta::CPAN
package App::SimplenoteSync;
$App::SimplenoteSync::VERSION = '0.2.1';
# ABSTRACT: Synchronise text notes with simplenoteapp.com
use v5.10;
use open qw(:std :utf8);
use Moose;
use MooseX::Types::Path::Class;
use Log::Any qw//;
use DateTime;
use Try::Tiny;
use File::ExtAttr ':all';
use Proc::InvokeEditor;
use App::SimplenoteSync::Note;
use WebService::Simplenote;
use Method::Signatures;
use namespace::autoclean;
has ['email', 'password'] => (
is => 'ro',
isa => 'Str',
required => 1,
);
has notes => (
is => 'rw',
traits => ['Hash'],
isa => 'HashRef[App::SimplenoteSync::Note]',
default => sub { {} },
handles => {
set_note => 'set',
has_note => 'exists',
num_notes => 'count',
remove_note => 'delete',
note_kvs => 'kv',
},
);
has stats => (
is => 'rw',
isa => 'HashRef',
default => sub {
{
new_local => 0,
new_remote => 0,
update_local => 0,
update_remote => 0,
deleted_local => 0,
trash => 0,
local_files => 0,
};
},
);
has simplenote => (
is => 'rw',
isa => 'WebService::Simplenote',
lazy => 1,
default => sub {
my $self = shift;
return WebService::Simplenote->new(
email => $self->email,
password => $self->password,
no_server_updates => $self->no_server_updates,
);
},
);
has ['no_server_updates', 'no_local_updates'] => (
is => 'ro',
lib/App/SimplenoteSync.pm view on Meta::CPAN
return 1;
}
method _put_note(App::SimplenoteSync::Note $note) {
if (!defined $note->content) {
$note->load_content || return;
}
$self->logger->infof('Uploading file: [%s]', $note->file->stringify);
my $key = $self->simplenote->put_note($note);
if (!$key) {
return;
}
$note->key($key);
return 1;
}
method merge_conflicts {
# Both the local copy and server copy were changed since last sync
# We'll merge the changes into a new master file, and flag any conflicts
}
method _merge_local_and_remote_lists(HashRef $remote_notes) {
$self->logger->debug("Comparing local and remote lists");
while (my ($key, $remote_note) = each %$remote_notes) {
if ($self->has_note($key)) {
my $local_note = $self->notes->{$key};
if ($local_note->ignored) {
$self->logger->debug("[$key] is being ignored");
next;
}
$self->logger->debug("[$key] exists locally and remotely");
if ($remote_note->deleted) {
$self->logger->warnf(
"[$key] has been trashed remotely. Deleting local copy in [%s]",
$local_note->file->stringify
);
$self->_delete_note($local_note);
next;
}
# which is newer?
# utime doesn't use nanoseconds
$remote_note->modifydate->set_nanosecond(0);
$self->logger->debugf(
'Comparing dates: remote [%s] // local [%s]',
$remote_note->modifydate->iso8601,
$local_note->modifydate->iso8601
);
given (
DateTime->compare_ignore_floating(
$remote_note->modifydate, $local_note->modifydate
))
{
when (0) {
$self->logger->debug("[$key] not modified");
}
when (1) {
$self->logger->debug("[$key] remote note is newer");
$self->_get_note($key);
$self->stats->{update_remote}++;
}
when (-1) {
$self->logger->debug("[$key] local note is newer");
$self->_put_note($local_note);
$self->stats->{update_local}++;
}
}
} else {
$self->logger->debug("[$key] does not exist locally");
if (!$remote_note->deleted) {
$self->_get_note($key);
} else {
$self->stats->{trash}++;
}
}
}
# try the other way to catch deleted notes
while (my ($key, $local_note) = each %{$self->notes}) {
if (!exists $remote_notes->{$key}) {
# if a local file has metadata, specifically simplenote.key
# but doesn't exist remotely it must have been deleted there
$self->logger->warnf(
"[$key] does not exist remotely. Deleting local copy in [%s]",
$local_note->file->stringify
);
$self->_delete_note($local_note);
}
}
return 1;
}
# TODO: check ctime
# XXX: this isn't called anywhere?!?
method _update_dates(App::SimplenoteSync::Note $note, Path::Class::File $file)
{
my $mod_time = DateTime->from_epoch(epoch => $file->stat->mtime);
given (DateTime->compare($mod_time, $note->modifydate)) {
when (0) {
# no change
return;
}
when (1) {
# file has changed
$note->modifydate($mod_time);
}
when (-1) {
die "File is older than sync db record?? Don't know what to do!\n";
}
}
return 1;
}
method _process_local_notes {
my $num_files = scalar $self->notes_dir->children(no_hidden => 1);
$self->logger->infof('Scanning [%d] items in [%s]',
$num_files, $self->notes_dir->stringify);
while (my $f = $self->notes_dir->next) {
next unless -f $f;
$self->logger->debug("Checking local file [$f]");
$self->stats->{local_files}++;
next if $f !~ /\.(txt|mkdn)$/;
my $note = App::SimplenoteSync::Note->new(
createdate => $f->stat->ctime,
modifydate => $f->stat->mtime,
file => $f,
);
if (!$self->_read_note_metadata($note)) {
$self->logger->info(
"Don't have a key for [$f], assuming it is new");
$self->_put_note($note);
$self->_write_note_metadata($note);
$self->stats->{new_local}++;
}
if (!defined $note->key) {
$self->logger->errorf("Skipping [%s]: failed to find a key",
$note->file->basename);
next;
}
my $key = $note->key;
if ($self->has_note($key)) {
$self->logger->error(
"[$key] Already have this key: title/filename clash??");
$self->logger->errorf('[%s] vs [%s]', $note->file->basename,
( run in 0.507 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )