File Coverage

blib/lib/WebService/Simplenote.pm
Criterion Covered Total %
statement 49 170 28.8
branch 0 70 0.0
condition 0 27 0.0
subroutine 22 30 73.3
pod n/a
total 71 297 23.9


line stmt bran cond sub pod time code
1             package WebService::Simplenote;
2              
3             # ABSTRACT: Note-taking through simplenoteapp.com
4              
5             # TODO: Net::HTTP::Spore?
6              
7             our $VERSION = '0.2.2';
8              
9 1     1   104158 use v5.10;
  1         13  
10 1     1   521 use open qw(:std :utf8);
  1         1662  
  1         7  
11 1     1   764 use Moose;
  1         529819  
  1         9  
12 1     1   8904 use MooseX::Types::Path::Class;
  1         170140  
  1         8  
13 1     1   1977 use JSON;
  1         11345  
  1         6  
14 1     1   914 use LWP::UserAgent;
  1         46804  
  1         50  
15 1     1   678 use HTTP::Cookies;
  1         7552  
  1         42  
16 1     1   522 use Log::Any qw//;
  1         9003  
  1         28  
17 1     1   949 use DateTime;
  1         504211  
  1         63  
18 1     1   730 use MIME::Base64 qw//;
  1         782  
  1         31  
19 1     1   8 use Try::Tiny;
  1         2  
  1         76  
20 1     1   598 use WebService::Simplenote::Note;
  1         5  
  1         100  
21 1     1   17 use Method::Signatures;
  1         4  
  1         14  
22 1     1   537 use namespace::autoclean;
  1         3  
  1         13  
23              
24             has ['email', 'password'] => (
25             is => 'ro',
26             isa => 'Str',
27             required => 1,
28             );
29              
30             has _token => (
31             is => 'rw',
32             isa => 'Str',
33             predicate => 'has_logged_in',
34             );
35              
36             has no_server_updates => (
37             is => 'ro',
38             isa => 'Bool',
39             required => 1,
40             default => 0,
41             );
42              
43             has page_size => (
44             is => 'ro',
45             isa => 'Int',
46             required => 1,
47             default => 20,
48             );
49              
50             has logger => (
51             is => 'ro',
52             isa => 'Object',
53             lazy => 1,
54             required => 1,
55             default => sub { return Log::Any->get_logger },
56             );
57              
58             has _uri => (
59             is => 'ro',
60             isa => 'Str',
61             default => 'https://simple-note.appspot.com/api2',
62             required => 1,
63             );
64              
65             has _ua => (
66             is => 'ro',
67             isa => 'LWP::UserAgent',
68             required => 1,
69             lazy_build => 1,
70             );
71              
72 1 0   1   1257 method _build__ua {
  0     0      
  0            
73              
74 0           my $headers = HTTP::Headers->new(Content_Type => 'application/json',);
75              
76             # XXX is it worth saving cookie?? How is password more valuable than auth token?
77             # logging in is only a fraction of a second!
78 0           my $ua = LWP::UserAgent->new(
79             agent => "WebService::Simplenote/$VERSION",
80             default_headers => $headers,
81             env_proxy => 1,
82             cookie_jar => HTTP::Cookies->new,
83             );
84              
85 0           return $ua;
86             }
87              
88             # Connect to server and get a authentication token
89 1 0   1   937 method _login {
  0     0      
  0            
90 0           my $content = MIME::Base64::encode_base64(sprintf 'email=%s&password=%s',
91             $self->email, $self->password);
92              
93 0           $self->logger->debug('Network: getting auth token');
94              
95             # the login uri uses api instead of api2 and must always be https
96 0           my $response =
97             $self->_ua->post('https://simple-note.appspot.com/api/login',
98             Content => $content);
99              
100 0 0         if (!$response->is_success) {
101 0           die 'Error logging into Simplenote server: '
102             . $response->status_line . "\n";
103             }
104              
105 0           $self->_token($response->content);
106 0           return 1;
107             }
108              
109 1 0 0 1   131397 method _build_req_uri(Str $path, HashRef $options?) {
  0 0 0 0      
  0 0 0        
  0 0          
  0            
  0            
  0            
  0            
110 0           my $req_uri = sprintf '%s/%s', $self->_uri, $path;
111              
112 0 0         if (!$self->has_logged_in) {
113 0           $self->_login;
114             }
115              
116 0 0         return $req_uri if !defined $options;
117              
118 0           $req_uri .= '?';
119 0           while (my ($option, $value) = each %$options) {
120 0           $req_uri .= "&$option=$value";
121             }
122              
123 0           return $req_uri;
124             }
125              
126 1 0 0 1   4406 method _get_remote_index_page(Str $mark?) {
  0 0 0 0      
  0            
  0            
  0            
127 0           my $notes;
128              
129 0           my $req_uri = $self->_build_req_uri('index', {length => $self->page_size});
130              
131 0 0         if (defined $mark) {
132 0           $self->logger->debug('Network: retrieving next page');
133 0           $req_uri .= '&mark=' . $mark;
134             }
135              
136 0           $self->logger->debug('Network: retrieving ' . $req_uri);
137              
138 0           my $response = $self->_ua->get($req_uri);
139 0 0         if (!$response->is_success) {
140 0           $self->logger->error('Network: ' . $response->status_line);
141 0           return;
142             }
143              
144 0           my $index = decode_json($response->content);
145              
146 0 0 0       if ($index->{count} > 0) {
    0          
    0          
147             $self->logger->debugf('Network: Index returned [%s] notes',
148 0           $index->{count});
149              
150             # iterate through notes in index and load into hash
151 0           foreach my $i (@{$index->{data}}) {
  0            
152 0           $notes->{$i->{key}} = WebService::Simplenote::Note->new($i);
153             }
154              
155             } elsif ($index->{count} == 0 && !exists $index->{mark}) {
156 0           $self->logger->debugf('Network: No more pages to retrieve');
157             } elsif ($index->{count} == 0) {
158 0           $self->logger->debugf('Network: No notes found');
159             }
160              
161 0 0         if (exists $index->{mark}) {
162 0           return ($notes, $index->{mark});
163             }
164              
165 0           return $notes;
166             }
167              
168             # Get list of notes from simplenote server
169             # TODO since, length options
170 1 0   1   1542 method get_remote_index {
  0     0      
  0            
171 0           $self->logger->debug('Network: getting note index');
172              
173 0           my ($notes, $mark) = $self->_get_remote_index_page;
174              
175 0           while (defined $mark) {
176 0           my $next_page;
177 0           ($next_page, $mark) = $self->_get_remote_index_page($mark);
178 0           @$notes{keys %$next_page} = values %$next_page;
179             }
180              
181 0           $self->logger->infof('Network: found %i remote notes',
182             scalar keys %$notes);
183 0           return $notes;
184             }
185              
186             # Given a local file, upload it as a note at simplenote web server
187 1 0 0 1   3747 method put_note(WebService::Simplenote::Note $note) {
  0 0   0      
  0 0          
  0            
  0            
  0            
188              
189 0 0         if ($self->no_server_updates) {
190 0           $self->logger->warn('Sending notes to the server is disabled');
191 0           return;
192             }
193              
194 0           my $req_uri = $self->_build_req_uri('data');
195              
196 0 0         if (defined $note->key) {
197 0           $self->logger->infof('[%s] Updating existing note', $note->key);
198 0           $req_uri .= '/' . $note->key,;
199             } else {
200 0           $self->logger->debug('Uploading new note');
201             }
202              
203 0           $self->logger->debug("Network: POST to [$req_uri]");
204              
205 0           my $content = $note->serialise;
206              
207 0           my $response = $self->_ua->post($req_uri, Content => $content);
208              
209 0 0         if (!$response->is_success) {
210 0           $self->logger->errorf('Failed uploading note: %s',
211             $response->status_line);
212 0           return;
213             }
214              
215 0           my $note_tmp = WebService::Simplenote::Note->new($response->content);
216              
217             # a brand new note will have a key generated remotely
218 0 0         if (!defined $note->key) {
219 0           return $note_tmp->key;
220             }
221              
222             #TODO better return values
223 0           return;
224             }
225              
226             # Save local copy of note from Simplenote server
227 1 0 0 1   4209 method get_note(Str $key) {
  0 0   0      
  0 0          
  0            
  0            
  0            
228 0           $self->logger->infof('Retrieving note [%s]', $key);
229              
230             # TODO are there any other encoding options?
231 0           my $req_uri = $self->_build_req_uri("data/$key");
232 0           $self->logger->debug("Network: GETting [$req_uri]");
233 0           my $response = $self->_ua->get($req_uri);
234              
235 0 0         if (!$response->is_success) {
236 0           $self->logger->errorf('[%s] could not be retrieved: %s',
237             $key, $response->status_line);
238 0           return;
239             }
240              
241 0           my $note = WebService::Simplenote::Note->new($response->content);
242              
243 0           return $note;
244             }
245              
246             # Delete specified note from Simplenote server
247 1 0 0 1   4048 method delete_note(WebService::Simplenote::Note $note) {
  0 0   0      
  0 0          
  0            
  0            
  0            
248              
249 0 0         if ($self->no_server_updates) {
250 0           $self->logger->warnf(
251             '[%s] Attempted to delete note when "no_server_updates" is set',
252             $note->key);
253 0           return;
254             }
255              
256 0 0         if (!$note->deleted) {
257 0           $self->logger->warnf(
258             '[%s] Attempted to delete note which was not marked as trash',
259             $note->key);
260 0           return;
261             }
262              
263 0           $self->logger->infof('[%s] Deleting from trash', $note->key);
264 0           my $req_uri = $self->_build_req_uri('data/' . $note->key);
265 0           $self->logger->debug("Network: DELETE on [$req_uri]");
266 0           my $response = $self->_ua->delete($req_uri);
267              
268 0 0         if (!$response->is_success) {
269 0           $self->logger->errorf('[%s] Failed to delete note from trash: %s',
270             $note->key, $response->status_line);
271 0           $self->logger->debug("Uri: [$req_uri]");
272 0           return;
273             }
274              
275 0           return 1;
276             }
277              
278             __PACKAGE__->meta->make_immutable;
279              
280             1;
281              
282             __END__
283              
284             =pod
285              
286             =encoding UTF-8
287              
288             =for :stopwords Ioan Rogers Fletcher T. Penney github
289              
290             =head1 NAME
291              
292             WebService::Simplenote - Note-taking through simplenoteapp.com
293              
294             =head1 VERSION
295              
296             version 0.2.2
297              
298             =head1 SYNOPSIS
299              
300             use WebService::Simplenote;
301             use WebService::Simplenote::Note;
302              
303             my $sn = WebService::Simplenote->new(
304             email => $email,
305             password => $password,
306             );
307              
308             my $notes = $sn->get_remote_index;
309              
310             foreach my $note_id (keys %$notes) {
311             say "Retrieving note id [$note_id]";
312             my $note = $sn->get_note($note_id);
313             printf "[%s] %s\n %s\n",
314             $note->modifydate->iso8601,
315             $note->title,
316             $note->content;
317             }
318              
319             my $new_note = WebService::Simplenote::Note->new(
320             content => "Some stuff",
321             );
322              
323             $sn->put_note($new_note);
324              
325             =head1 DESCRIPTION
326              
327             This module proves v2.1.5 API access to the cloud-based note software at
328             L<Simplenote|https://simplenoteapp.com>.
329              
330             =head1 ERRORS
331              
332             Will C<die> if unable to connect/login. Returns C<undef> for other errors.
333              
334             =head1 METHODS
335              
336             =over
337              
338             =item WebService::Simplenote->new($args)
339              
340             Requires the C<email> and C<password> for your simplenote account. You can also
341             provide a L<Log::Any> compatible C<logger>.
342              
343             =item get_remote_index
344              
345             Returns a hashref of L<WebService::Simplenote::Note|notes>. The notes are keyed by id.
346              
347             =item get_note($note_id)
348              
349             Retrieves a note from the remote server and returns it as a L<WebService::Simplenote::Note>.
350             C<$note_id> is an alphanumeric key generated on the server side.
351              
352             =item put_note($note)
353              
354             Puts a L<WebService::Simplenote::Note> to the remote server
355              
356             =item delete_note($note_id)
357              
358             Delete the specified note from the server. The note should be marked as C<deleted>
359             beforehand.
360              
361             =back
362              
363             =head1 TESTING
364              
365             Setting the environment variables C<SIMPLENOTE_USER> and C<SIMPLENOTE_PASS> will enable remote tests.
366             If you want to run the remote tests B<MAKE SURE YOU MAKE A BACKUP OF YOUR NOTES FIRST!!>
367              
368             =head1 SEE ALSO
369              
370             Designed for use with Simplenote:
371              
372             <http://www.simplenoteapp.com/>
373              
374             Based on SimplenoteSync:
375              
376             <http://fletcherpenney.net/other_projects/simplenotesync/>
377              
378             =head1 AUTHORS
379              
380             =over 4
381              
382             =item *
383              
384             Ioan Rogers <ioanr@cpan.org>
385              
386             =item *
387              
388             Fletcher T. Penney <owner@fletcherpenney.net>
389              
390             =back
391              
392             =head1 COPYRIGHT AND LICENSE
393              
394             This software is Copyright (c) 2021 by Ioan Rogers.
395              
396             This is free software, licensed under:
397              
398             The GNU General Public License, Version 2, June 1991
399              
400             =head1 BUGS AND LIMITATIONS
401              
402             You can make new bug reports, and view existing ones, through the
403             web interface at L<https://github.com/ioanrogers/WebService-Simplenote/issues>.
404              
405             =head1 SOURCE
406              
407             The development version is on github at L<https://github.com/ioanrogers/WebService-Simplenote>
408             and may be cloned from L<git://github.com/ioanrogers/WebService-Simplenote.git>
409              
410             =cut