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 )