| line | stmt | bran | cond | sub | pod | time | code | 
| 1 | 2 |  |  | 2 |  | 347193 | use strict; | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 80 |  | 
| 2 | 2 |  |  | 2 |  | 10 | use warnings; | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 81 |  | 
| 3 |  |  |  |  |  |  |  | 
| 4 |  |  |  |  |  |  | package WebService::SlimTimer; | 
| 5 |  |  |  |  |  |  |  | 
| 6 |  |  |  |  |  |  | # ABSTRACT: Provides interface to SlimTimer web service. | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  |  | 
| 9 | 2 |  |  | 2 |  | 9688 | use Moose; | 
|  | 2 |  |  |  |  | 1029793 |  | 
|  | 2 |  |  |  |  | 19 |  | 
| 10 | 2 |  |  | 2 |  | 18778 | use MooseX::Method::Signatures; | 
|  | 2 |  |  |  |  | 3189034 |  | 
|  | 2 |  |  |  |  | 15 |  | 
| 11 | 2 |  |  | 2 |  | 450 | use Moose::Util::TypeConstraints; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 27 |  | 
| 12 | 2 |  |  | 2 |  | 4887 | use MooseX::Types::Moose qw(Int Str); | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 25 |  | 
| 13 |  |  |  |  |  |  |  | 
| 14 | 2 |  |  | 2 |  | 17621 | use LWP::UserAgent; | 
|  | 2 |  |  |  |  | 150000 |  | 
|  | 2 |  |  |  |  | 76 |  | 
| 15 | 2 |  |  | 2 |  | 1803 | use YAML::XS; | 
|  | 2 |  |  |  |  | 6304 |  | 
|  | 2 |  |  |  |  | 155 |  | 
| 16 |  |  |  |  |  |  |  | 
| 17 | 2 |  |  | 2 |  | 1277 | use WebService::SlimTimer::Task; | 
|  | 2 |  |  |  |  | 960 |  | 
|  | 2 |  |  |  |  | 75 |  | 
| 18 | 2 |  |  | 2 |  | 1630 | use WebService::SlimTimer::TimeEntry; | 
|  | 2 |  |  |  |  | 765 |  | 
|  | 2 |  |  |  |  | 95 |  | 
| 19 | 2 |  |  | 2 |  | 29 | use WebService::SlimTimer::Types qw(TimeStamp OptionalTimeStamp); | 
|  | 2 |  |  |  |  | 11 |  | 
|  | 2 |  |  |  |  | 23 |  | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | our $VERSION = '0.005'; # VERSION | 
| 22 |  |  |  |  |  |  | our $DEBUG = 0; | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | has api_key => ( is => 'ro', isa => Str, required => 1 ); | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | has user_id => ( is => 'ro', isa => Int, writer => '_set_user_id' ); | 
| 27 |  |  |  |  |  |  | has access_token => ( is => 'ro', isa => Str, writer => '_set_access_token', | 
| 28 |  |  |  |  |  |  | predicate => 'is_logged_in' | 
| 29 |  |  |  |  |  |  | ); | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | has _user_agent => ( is => 'ro', builder => '_create_ua', lazy => 1 ); | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | # Returns just the first line of possibly multiline string passed as argument. | 
| 34 |  |  |  |  |  |  | sub _first_line | 
| 35 |  |  |  |  |  |  | { | 
| 36 | 0 |  |  | 0 |  |  | my $text = shift; | 
| 37 | 0 |  |  |  |  |  | $text =~ s/\n.*//s; | 
| 38 | 0 |  |  |  |  |  | return $text; | 
| 39 |  |  |  |  |  |  | } | 
| 40 |  |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  | # Return a string representation of a TimeStamp. | 
| 42 | 2 |  |  | 2 |  | 116331 | method _format_time(TimeStamp $timestamp) { | 
| 43 | 2 |  |  | 2 |  | 199 | use DateTime::Format::RFC3339; | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 110 |  | 
| 44 |  |  |  |  |  |  | return DateTime::Format::RFC3339->format_datetime($timestamp) | 
| 45 |  |  |  |  |  |  | } | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | # Create the LWP object that we use. This is currently trivial but provides a | 
| 48 |  |  |  |  |  |  | # central point for customizing its creation later. | 
| 49 | 2 |  |  | 2 |  | 55717 | method _create_ua() { | 
| 50 |  |  |  |  |  |  | my $ua = LWP::UserAgent->new; | 
| 51 |  |  |  |  |  |  | return $ua; | 
| 52 |  |  |  |  |  |  | } | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | # Common part of _request() and _post(): submit the request and check that it | 
| 55 |  |  |  |  |  |  | # didn't fail. | 
| 56 | 2 |  |  | 2 |  | 148856 | method _submit($req, Str $error) { | 
| 57 |  |  |  |  |  |  | my $res = $self->_user_agent->request($req); | 
| 58 |  |  |  |  |  |  |  | 
| 59 |  |  |  |  |  |  | print DateTime->now() . ": received reply.\n" if $DEBUG; | 
| 60 |  |  |  |  |  |  | print "Reply contents:\n" . $res->content . "\n" if $DEBUG >= 2; | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | if ( !$res->is_success ) { | 
| 63 |  |  |  |  |  |  | die "$error: " . $res->status_line | 
| 64 |  |  |  |  |  |  | } | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | return Load($res->content) | 
| 67 |  |  |  |  |  |  | } | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | # A helper method for creating and submitting an HTTP request without | 
| 70 |  |  |  |  |  |  | # any body parameters, e.g. a GET or DELETE. | 
| 71 | 2 |  |  | 2 |  | 403818 | method _request(Str $method, Str $url, Str :$error!, HashRef :$params) { | 
| 72 |  |  |  |  |  |  | my $uri = URI->new($url); | 
| 73 |  |  |  |  |  |  | $uri->query_form( | 
| 74 |  |  |  |  |  |  | api_key => $self->api_key, | 
| 75 |  |  |  |  |  |  | access_token => $self->access_token, | 
| 76 |  |  |  |  |  |  | %$params | 
| 77 |  |  |  |  |  |  | ); | 
| 78 |  |  |  |  |  |  | my $req = HTTP::Request->new($method, $uri); | 
| 79 |  |  |  |  |  |  |  | 
| 80 |  |  |  |  |  |  | print DateTime->now() . ": " . _first_line($req->as_string) . ".\n" if $DEBUG; | 
| 81 |  |  |  |  |  |  |  | 
| 82 |  |  |  |  |  |  | $req->header(Accept => 'application/x-yaml'); | 
| 83 |  |  |  |  |  |  |  | 
| 84 |  |  |  |  |  |  | return $self->_submit($req, $error) | 
| 85 |  |  |  |  |  |  | } | 
| 86 |  |  |  |  |  |  |  | 
| 87 |  |  |  |  |  |  | # Another helper for POST and PUT requests. | 
| 88 | 2 |  |  | 2 |  | 544545 | method _post(Str $method, Str $url, HashRef $params, Str :$error!) { | 
| 89 |  |  |  |  |  |  | my $req = HTTP::Request->new($method, $url); | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | $params->{'api_key'} = $self->api_key; | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | # POST request is used to log in so we can be called before we have the | 
| 94 |  |  |  |  |  |  | # access token and need to check for this explicitly. | 
| 95 |  |  |  |  |  |  | if ( $self->is_logged_in ) { | 
| 96 |  |  |  |  |  |  | $params->{'access_token'} = $self->access_token; | 
| 97 |  |  |  |  |  |  | } | 
| 98 |  |  |  |  |  |  |  | 
| 99 |  |  |  |  |  |  | print DateTime->now() . ": " . _first_line($req->as_string) . ".\n" if $DEBUG; | 
| 100 |  |  |  |  |  |  |  | 
| 101 |  |  |  |  |  |  | $req->content(Dump($params)); | 
| 102 |  |  |  |  |  |  |  | 
| 103 |  |  |  |  |  |  | print "Parameters:\n" . $req->as_string . "\n" if $DEBUG >= 2; | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | $req->header(Accept => 'application/x-yaml'); | 
| 106 |  |  |  |  |  |  | $req->content_type('application/x-yaml'); | 
| 107 |  |  |  |  |  |  |  | 
| 108 |  |  |  |  |  |  | return $self->_submit($req, $error) | 
| 109 |  |  |  |  |  |  | } | 
| 110 |  |  |  |  |  |  |  | 
| 111 |  |  |  |  |  |  |  | 
| 112 |  |  |  |  |  |  | # Provide a simple single-argument ctor instead of default Moose one taking a | 
| 113 |  |  |  |  |  |  | # hash with all attributes values. | 
| 114 |  |  |  |  |  |  | around BUILDARGS => sub | 
| 115 |  |  |  |  |  |  | { | 
| 116 |  |  |  |  |  |  | die "A single API key argument is required" unless @_ == 3; | 
| 117 |  |  |  |  |  |  |  | 
| 118 |  |  |  |  |  |  | my ($orig, $class, $api_key) = @_; | 
| 119 |  |  |  |  |  |  |  | 
| 120 |  |  |  |  |  |  | $class->$orig(api_key => $api_key) | 
| 121 |  |  |  |  |  |  | }; | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  |  | 
| 124 | 2 |  |  | 2 |  | 234760 | method login(Str $login, Str $password) { | 
| 125 |  |  |  |  |  |  | my $res = $self->_post(POST => 'http://slimtimer.com/users/token', | 
| 126 |  |  |  |  |  |  | { user => { email => $login, password => $password } }, | 
| 127 |  |  |  |  |  |  | error => "Failed to login as \"$login\"" | 
| 128 |  |  |  |  |  |  | ); | 
| 129 |  |  |  |  |  |  |  | 
| 130 |  |  |  |  |  |  | $self->_set_user_id($res->{user_id}); | 
| 131 |  |  |  |  |  |  | $self->_set_access_token($res->{access_token}) | 
| 132 |  |  |  |  |  |  | } | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  |  | 
| 135 |  |  |  |  |  |  | # Helper for task-related methods: returns either the root tasks URI or the | 
| 136 |  |  |  |  |  |  | # URI for the given task if the task id is specified. | 
| 137 | 2 |  |  | 2 |  | 129325 | method _get_tasks_uri(Int $task_id?) { | 
| 138 |  |  |  |  |  |  | my $uri = "http://slimtimer.com/users/$self->{user_id}/tasks"; | 
| 139 |  |  |  |  |  |  | if ( defined $task_id ) { | 
| 140 |  |  |  |  |  |  | $uri .= "/$task_id" | 
| 141 |  |  |  |  |  |  | } | 
| 142 |  |  |  |  |  |  |  | 
| 143 |  |  |  |  |  |  | return $uri | 
| 144 |  |  |  |  |  |  | } | 
| 145 |  |  |  |  |  |  |  | 
| 146 |  |  |  |  |  |  |  | 
| 147 | 2 |  |  | 2 |  | 107884 | method list_tasks(Bool $include_completed = 1) { | 
| 148 |  |  |  |  |  |  | my $tasks_entries = $self->_request(GET => $self->_get_tasks_uri, | 
| 149 |  |  |  |  |  |  | params => { | 
| 150 |  |  |  |  |  |  | show_completed => $include_completed ? 'yes' : 'no' | 
| 151 |  |  |  |  |  |  | }, | 
| 152 |  |  |  |  |  |  | error => "Failed to get the tasks list" | 
| 153 |  |  |  |  |  |  | ); | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | # The expected reply structure is an array of hashes corresponding to each | 
| 156 |  |  |  |  |  |  | # task. | 
| 157 |  |  |  |  |  |  | my @tasks; | 
| 158 |  |  |  |  |  |  | for (@$tasks_entries) { | 
| 159 |  |  |  |  |  |  | push @tasks, WebService::SlimTimer::Task->new(%$_); | 
| 160 |  |  |  |  |  |  | } | 
| 161 |  |  |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | return @tasks; | 
| 163 |  |  |  |  |  |  | } | 
| 164 |  |  |  |  |  |  |  | 
| 165 |  |  |  |  |  |  |  | 
| 166 | 2 |  |  | 2 |  | 146567 | method create_task(Str $name) { | 
| 167 |  |  |  |  |  |  | my $res = $self->_post(POST => $self->_get_tasks_uri, | 
| 168 |  |  |  |  |  |  | { task => { name => $name } }, | 
| 169 |  |  |  |  |  |  | error => "Failed to create task \"$name\"" | 
| 170 |  |  |  |  |  |  | ); | 
| 171 |  |  |  |  |  |  |  | 
| 172 |  |  |  |  |  |  | return WebService::SlimTimer::Task->new($res); | 
| 173 |  |  |  |  |  |  | } | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  |  | 
| 176 | 2 |  |  | 2 |  | 195514 | method delete_task(Int $task_id) { | 
| 177 |  |  |  |  |  |  | $self->_request(DELETE => $self->_get_tasks_uri($task_id), | 
| 178 |  |  |  |  |  |  | error => "Failed to delete the task $task_id" | 
| 179 |  |  |  |  |  |  | ); | 
| 180 |  |  |  |  |  |  | } | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  |  | 
| 183 | 2 |  |  | 2 |  | 208591 | method get_task(Int $task_id) { | 
| 184 |  |  |  |  |  |  | my $res = $self->_request(GET => $self->_get_tasks_uri($task_id), | 
| 185 |  |  |  |  |  |  | error => "Failed to find the task $task_id" | 
| 186 |  |  |  |  |  |  | ); | 
| 187 |  |  |  |  |  |  |  | 
| 188 |  |  |  |  |  |  | return WebService::SlimTimer::Task->new($res); | 
| 189 |  |  |  |  |  |  | } | 
| 190 |  |  |  |  |  |  |  | 
| 191 |  |  |  |  |  |  |  | 
| 192 | 2 |  |  | 2 |  | 266739 | method complete_task(Int $task_id, TimeStamp $completed_on) { | 
| 193 |  |  |  |  |  |  | $self->_post(PUT => $self->_get_tasks_uri($task_id), | 
| 194 |  |  |  |  |  |  | { task => { completed_on => $self->_format_time($completed_on) } }, | 
| 195 |  |  |  |  |  |  | error => "Failed to mark the task $task_id as completed" | 
| 196 |  |  |  |  |  |  | ); | 
| 197 |  |  |  |  |  |  | } | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  |  | 
| 201 |  |  |  |  |  |  | # Helper for time-entry-related methods: returns either the root time entries | 
| 202 |  |  |  |  |  |  | # URI or the URI for the given entry if the time entry id is specified. | 
| 203 | 2 |  |  | 2 |  | 189110 | method _get_entries_uri(Int $entry_id?) { | 
| 204 |  |  |  |  |  |  | my $uri = "http://slimtimer.com/users/$self->{user_id}/time_entries"; | 
| 205 |  |  |  |  |  |  | if ( defined $entry_id ) { | 
| 206 |  |  |  |  |  |  | $uri .= "/$entry_id" | 
| 207 |  |  |  |  |  |  | } | 
| 208 |  |  |  |  |  |  |  | 
| 209 |  |  |  |  |  |  | return $uri | 
| 210 |  |  |  |  |  |  | } | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  | # Common part of list_entries() and list_task_entries() | 
| 213 | 2 |  |  | 2 |  | 343303 | method _list_entries( | 
| 214 |  |  |  |  |  |  | Maybe[Int] $taskId, | 
| 215 |  |  |  |  |  |  | OptionalTimeStamp $start, | 
| 216 |  |  |  |  |  |  | OptionalTimeStamp $end) { | 
| 217 |  |  |  |  |  |  | my $uri = defined $taskId | 
| 218 |  |  |  |  |  |  | ? $self->_get_tasks_uri($taskId) . "/time_entries" | 
| 219 |  |  |  |  |  |  | : $self->_get_entries_uri; | 
| 220 |  |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | my %params; | 
| 222 |  |  |  |  |  |  | $params{'range_start'} = $self->_format_time($start) if defined $start; | 
| 223 |  |  |  |  |  |  | $params{'range_end'} = $self->_format_time($end) if defined $end; | 
| 224 |  |  |  |  |  |  |  | 
| 225 |  |  |  |  |  |  | my $entries = $self->_request(GET => $uri, | 
| 226 |  |  |  |  |  |  | params => \%params, | 
| 227 |  |  |  |  |  |  | error => "Failed to get the entries list" | 
| 228 |  |  |  |  |  |  | ); | 
| 229 |  |  |  |  |  |  |  | 
| 230 |  |  |  |  |  |  | my @time_entries; | 
| 231 |  |  |  |  |  |  | for (@$entries) { | 
| 232 |  |  |  |  |  |  | push @time_entries, WebService::SlimTimer::TimeEntry->new($_); | 
| 233 |  |  |  |  |  |  | } | 
| 234 |  |  |  |  |  |  |  | 
| 235 |  |  |  |  |  |  | return @time_entries; | 
| 236 | 2 |  |  | 2 |  | 304975 | } | 
| 237 |  |  |  |  |  |  |  | 
| 238 |  |  |  |  |  |  |  | 
| 239 |  |  |  |  |  |  | method list_entries(TimeStamp :$start, TimeStamp :$end) { | 
| 240 |  |  |  |  |  |  | return $self->_list_entries(undef, $start, $end); | 
| 241 | 2 |  |  | 2 |  | 445812 | } | 
| 242 |  |  |  |  |  |  |  | 
| 243 |  |  |  |  |  |  |  | 
| 244 |  |  |  |  |  |  | method list_task_entries(Int $taskId, TimeStamp :$start, TimeStamp :$end) { | 
| 245 |  |  |  |  |  |  | return $self->_list_entries($taskId, $start, $end); | 
| 246 | 2 |  |  | 2 |  | 160646 | } | 
| 247 |  |  |  |  |  |  |  | 
| 248 |  |  |  |  |  |  |  | 
| 249 |  |  |  |  |  |  | method get_entry(Int $entryId) { | 
| 250 |  |  |  |  |  |  | my $res = $self->_request(GET => $self->_get_entries_uri($entryId), | 
| 251 |  |  |  |  |  |  | error => "Failed to get the entry $entryId" | 
| 252 |  |  |  |  |  |  | ); | 
| 253 |  |  |  |  |  |  |  | 
| 254 |  |  |  |  |  |  | return WebService::SlimTimer::TimeEntry->new($res); | 
| 255 | 2 |  |  | 2 |  | 395565 | } | 
| 256 |  |  |  |  |  |  |  | 
| 257 |  |  |  |  |  |  |  | 
| 258 |  |  |  |  |  |  | method create_entry(Int $taskId, TimeStamp $start, TimeStamp $end?) { | 
| 259 |  |  |  |  |  |  | $end = DateTime->now if !defined $end; | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | my $res = $self->_post(POST => $self->_get_entries_uri, { | 
| 262 |  |  |  |  |  |  | time_entry => { | 
| 263 |  |  |  |  |  |  | task_id => $taskId, | 
| 264 |  |  |  |  |  |  | start_time => $self->_format_time($start), | 
| 265 |  |  |  |  |  |  | end_time => $self->_format_time($end), | 
| 266 |  |  |  |  |  |  | duration_in_seconds => $end->epoch() - $start->epoch(), | 
| 267 |  |  |  |  |  |  | } | 
| 268 |  |  |  |  |  |  | }, | 
| 269 |  |  |  |  |  |  | error => "Failed to create new entry for task $taskId" | 
| 270 |  |  |  |  |  |  | ); | 
| 271 |  |  |  |  |  |  |  | 
| 272 |  |  |  |  |  |  | return WebService::SlimTimer::TimeEntry->new($res); | 
| 273 | 2 |  |  | 2 |  | 344299 | } | 
| 274 |  |  |  |  |  |  |  | 
| 275 |  |  |  |  |  |  |  | 
| 276 |  |  |  |  |  |  | method update_entry( | 
| 277 |  |  |  |  |  |  | Int $entry_id, | 
| 278 |  |  |  |  |  |  | Int $taskId, | 
| 279 |  |  |  |  |  |  | TimeStamp $start, | 
| 280 |  |  |  |  |  |  | TimeStamp $end) { | 
| 281 |  |  |  |  |  |  | $self->_post(PUT => $self->_get_entries_uri($entry_id), { | 
| 282 |  |  |  |  |  |  | time_entry => { | 
| 283 |  |  |  |  |  |  | task_id => $taskId, | 
| 284 |  |  |  |  |  |  | start_time => $self->_format_time($start), | 
| 285 |  |  |  |  |  |  | end_time => $self->_format_time($end), | 
| 286 |  |  |  |  |  |  | duration_in_seconds => $end->epoch() - $start->epoch(), | 
| 287 | 2 |  |  | 2 |  | 178253 | } | 
| 288 |  |  |  |  |  |  | }, | 
| 289 |  |  |  |  |  |  | error => "Failed to update the entry $entry_id" | 
| 290 |  |  |  |  |  |  | ); | 
| 291 |  |  |  |  |  |  | } | 
| 292 |  |  |  |  |  |  |  | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  | method delete_entry(Int $entry_id) { | 
| 295 |  |  |  |  |  |  | $self->_request(DELETE => $self->_get_entries_uri($entry_id), | 
| 296 |  |  |  |  |  |  | error => "Failed to delete the entry $entry_id" | 
| 297 |  |  |  |  |  |  | ); | 
| 298 |  |  |  |  |  |  | } | 
| 299 |  |  |  |  |  |  |  | 
| 300 |  |  |  |  |  |  | 1; | 
| 301 |  |  |  |  |  |  |  | 
| 302 |  |  |  |  |  |  | __END__ | 
| 303 |  |  |  |  |  |  | =pod | 
| 304 |  |  |  |  |  |  |  | 
| 305 |  |  |  |  |  |  | =head1 NAME | 
| 306 |  |  |  |  |  |  |  | 
| 307 |  |  |  |  |  |  | WebService::SlimTimer - Provides interface to SlimTimer web service. | 
| 308 |  |  |  |  |  |  |  | 
| 309 |  |  |  |  |  |  | =head1 VERSION | 
| 310 |  |  |  |  |  |  |  | 
| 311 |  |  |  |  |  |  | version 0.005 | 
| 312 |  |  |  |  |  |  |  | 
| 313 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 314 |  |  |  |  |  |  |  | 
| 315 |  |  |  |  |  |  | This module provides interface to L<http://www.slimtimer.com/> functionality. | 
| 316 |  |  |  |  |  |  |  | 
| 317 |  |  |  |  |  |  | Notice that to use it you must obtain an API key by creating an account at | 
| 318 |  |  |  |  |  |  | SlimTimer web site and then visit L<http://slimtimer.com/help/api>. | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  | my $st = WebService::SlimTimer->new($api_key); | 
| 321 |  |  |  |  |  |  | $st->login('your@email.address', 'secret-password'); | 
| 322 |  |  |  |  |  |  |  | 
| 323 |  |  |  |  |  |  | # Create a brand new task. | 
| 324 |  |  |  |  |  |  | my $task = $st->create_task('Testing SlimTimer'); | 
| 325 |  |  |  |  |  |  |  | 
| 326 |  |  |  |  |  |  | # Spend 10 minutes on testing. | 
| 327 |  |  |  |  |  |  | $st->create_entry($task->id, DateTime->now, DateTime->from_epoch(time() + 600)); | 
| 328 |  |  |  |  |  |  |  | 
| 329 |  |  |  |  |  |  | # Mark the task as completed. | 
| 330 |  |  |  |  |  |  | $st->complete_task($task->id, DateTime->now()); | 
| 331 |  |  |  |  |  |  |  | 
| 332 |  |  |  |  |  |  | # Or maybe even get rid of it now. | 
| 333 |  |  |  |  |  |  | $st->delete_task($task->id); | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | =head1 METHODS | 
| 336 |  |  |  |  |  |  |  | 
| 337 |  |  |  |  |  |  | =head2 CONSTRUCTOR | 
| 338 |  |  |  |  |  |  |  | 
| 339 |  |  |  |  |  |  | The single required constructor argument is the API key required to connect to | 
| 340 |  |  |  |  |  |  | SlimTimer: | 
| 341 |  |  |  |  |  |  |  | 
| 342 |  |  |  |  |  |  | my $st = WebService::SlimTimer->new('123456789abcdef123456789abcdef'); | 
| 343 |  |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  | The validity of the API key is not checked here but using an invalid key will | 
| 345 |  |  |  |  |  |  | result in a failure to C<login()> later. | 
| 346 |  |  |  |  |  |  |  | 
| 347 |  |  |  |  |  |  | =head2 login | 
| 348 |  |  |  |  |  |  |  | 
| 349 |  |  |  |  |  |  | Logs in to SlimTimer using the provided login and password. | 
| 350 |  |  |  |  |  |  |  | 
| 351 |  |  |  |  |  |  | $st->login('your@email.address', 'secret-password'); | 
| 352 |  |  |  |  |  |  |  | 
| 353 |  |  |  |  |  |  | This method must be called before doing anything else with this object. | 
| 354 |  |  |  |  |  |  |  | 
| 355 |  |  |  |  |  |  | =head2 list_tasks | 
| 356 |  |  |  |  |  |  |  | 
| 357 |  |  |  |  |  |  | Returns the list of all tasks involving the logged in user: | 
| 358 |  |  |  |  |  |  |  | 
| 359 |  |  |  |  |  |  | my @tasks = $st->list_tasks(); | 
| 360 |  |  |  |  |  |  | say join("\n", map { $_->name } @tasks); # Output the names of all tasks | 
| 361 |  |  |  |  |  |  |  | 
| 362 |  |  |  |  |  |  | By default all tasks are returned, even the completed ones. Passing a false | 
| 363 |  |  |  |  |  |  | value as parameter excludes the completed tasks: | 
| 364 |  |  |  |  |  |  |  | 
| 365 |  |  |  |  |  |  | my @active_tasks = $st->list_tasks(0); | 
| 366 |  |  |  |  |  |  |  | 
| 367 |  |  |  |  |  |  | See L<WebService::SlimTimer::Task> for the details of the returned objects. | 
| 368 |  |  |  |  |  |  |  | 
| 369 |  |  |  |  |  |  | =head2 create_task | 
| 370 |  |  |  |  |  |  |  | 
| 371 |  |  |  |  |  |  | Create a new task with the given name and returns the new | 
| 372 |  |  |  |  |  |  | L<WebService::SlimTimer::Task> object on success. | 
| 373 |  |  |  |  |  |  |  | 
| 374 |  |  |  |  |  |  | my $task = $st->create_task('Test Task'); | 
| 375 |  |  |  |  |  |  | ... Use $task->id with the other methods ... | 
| 376 |  |  |  |  |  |  |  | 
| 377 |  |  |  |  |  |  | =head2 delete_task | 
| 378 |  |  |  |  |  |  |  | 
| 379 |  |  |  |  |  |  | Delete the task with the given id (presumably previously obtained from | 
| 380 |  |  |  |  |  |  | L<list_tasks>). | 
| 381 |  |  |  |  |  |  |  | 
| 382 |  |  |  |  |  |  | $st->delete_task($task->id); | 
| 383 |  |  |  |  |  |  |  | 
| 384 |  |  |  |  |  |  | =head2 get_task | 
| 385 |  |  |  |  |  |  |  | 
| 386 |  |  |  |  |  |  | Find the given task by its id. | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | my $task = $st->get_task(task_id); | 
| 389 |  |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  | While there is no direct way to obtain a task id using this module, it could | 
| 391 |  |  |  |  |  |  | be cached locally from a previous program execution, for example. | 
| 392 |  |  |  |  |  |  |  | 
| 393 |  |  |  |  |  |  | =head2 complete_task | 
| 394 |  |  |  |  |  |  |  | 
| 395 |  |  |  |  |  |  | Mark the task with the given id as being completed. | 
| 396 |  |  |  |  |  |  |  | 
| 397 |  |  |  |  |  |  | $st->complete_task($task->id, DateTime->now); | 
| 398 |  |  |  |  |  |  |  | 
| 399 |  |  |  |  |  |  | =head2 list_entries | 
| 400 |  |  |  |  |  |  |  | 
| 401 |  |  |  |  |  |  | Return all the time entries. | 
| 402 |  |  |  |  |  |  |  | 
| 403 |  |  |  |  |  |  | If the optional C<start> and/or C<end> parameters are specified, returns only | 
| 404 |  |  |  |  |  |  | the entries that begin after the start date and/or before the end one. | 
| 405 |  |  |  |  |  |  |  | 
| 406 |  |  |  |  |  |  | # List all entries: potentially very time-consuming. | 
| 407 |  |  |  |  |  |  | my @entries = $st->list_entries; | 
| 408 |  |  |  |  |  |  |  | 
| 409 |  |  |  |  |  |  | # List entries started today. | 
| 410 |  |  |  |  |  |  | my $today = DateTime->now; | 
| 411 |  |  |  |  |  |  | $today->set(hour => 0, minute => 0, second => 0); | 
| 412 |  |  |  |  |  |  | my @today_entries = $st->list_entries(start => $today); | 
| 413 |  |  |  |  |  |  |  | 
| 414 |  |  |  |  |  |  | # List entries started in 2010. | 
| 415 |  |  |  |  |  |  | my @entries_2010 = $st->list_entries( | 
| 416 |  |  |  |  |  |  | start => DateTime->new(year => 2010), | 
| 417 |  |  |  |  |  |  | end => DateTime->new(year => 2011) | 
| 418 |  |  |  |  |  |  | ); | 
| 419 |  |  |  |  |  |  |  | 
| 420 |  |  |  |  |  |  | =head2 list_task_entries | 
| 421 |  |  |  |  |  |  |  | 
| 422 |  |  |  |  |  |  | Return all the time entries for the given task. | 
| 423 |  |  |  |  |  |  |  | 
| 424 |  |  |  |  |  |  | Just as L<list_entries>, this method accepts optional C<start> and C<end> | 
| 425 |  |  |  |  |  |  | parameters to restrict the dates of the entries retrieved. | 
| 426 |  |  |  |  |  |  |  | 
| 427 |  |  |  |  |  |  | my @today_work_on_task = $st->list_entries($task->id, start => $today); | 
| 428 |  |  |  |  |  |  |  | 
| 429 |  |  |  |  |  |  | =head2 get_entry | 
| 430 |  |  |  |  |  |  |  | 
| 431 |  |  |  |  |  |  | Find the given time entry by its id. | 
| 432 |  |  |  |  |  |  |  | 
| 433 |  |  |  |  |  |  | my $entry = $st->get_entry($entry_id); | 
| 434 |  |  |  |  |  |  |  | 
| 435 |  |  |  |  |  |  | As with C<get_task()>, it only makes sense to use this method if the id comes | 
| 436 |  |  |  |  |  |  | from a local cache. | 
| 437 |  |  |  |  |  |  |  | 
| 438 |  |  |  |  |  |  | =head2 create_entry | 
| 439 |  |  |  |  |  |  |  | 
| 440 |  |  |  |  |  |  | Create a new time entry. | 
| 441 |  |  |  |  |  |  |  | 
| 442 |  |  |  |  |  |  | my $day_of_work = $st->create_entry($task->id, | 
| 443 |  |  |  |  |  |  | DateTime->now->set(hour => 9, minute => 0, second = 0), | 
| 444 |  |  |  |  |  |  | DateTime->now->set(hour => 17, minute => 0, second = 0) | 
| 445 |  |  |  |  |  |  | ); | 
| 446 |  |  |  |  |  |  |  | 
| 447 |  |  |  |  |  |  | Notice that the time stamps should normally be in UTC and not local time or | 
| 448 |  |  |  |  |  |  | another time zone. | 
| 449 |  |  |  |  |  |  |  | 
| 450 |  |  |  |  |  |  | If the C<end> parameter is not specified, it defaults to now. | 
| 451 |  |  |  |  |  |  |  | 
| 452 |  |  |  |  |  |  | Returns the entry that was created. | 
| 453 |  |  |  |  |  |  |  | 
| 454 |  |  |  |  |  |  | =head2 update_entry | 
| 455 |  |  |  |  |  |  |  | 
| 456 |  |  |  |  |  |  | Changes an existing time entry. | 
| 457 |  |  |  |  |  |  |  | 
| 458 |  |  |  |  |  |  | # Use more realistic schedule. | 
| 459 |  |  |  |  |  |  | $st->update_entry($day_of_work->id, $task->id, | 
| 460 |  |  |  |  |  |  | DateTime->now->set(hour => 11, minute => 0, second = 0) | 
| 461 |  |  |  |  |  |  | DateTime->now->set(hour => 23, minute => 0, second = 0) | 
| 462 |  |  |  |  |  |  | ); | 
| 463 |  |  |  |  |  |  |  | 
| 464 |  |  |  |  |  |  | =head2 delete_entry | 
| 465 |  |  |  |  |  |  |  | 
| 466 |  |  |  |  |  |  | Deletes a time entry. | 
| 467 |  |  |  |  |  |  |  | 
| 468 |  |  |  |  |  |  | $st->delete_entry($day_of_work->id); | 
| 469 |  |  |  |  |  |  |  | 
| 470 |  |  |  |  |  |  | =head1 VARIABLES | 
| 471 |  |  |  |  |  |  |  | 
| 472 |  |  |  |  |  |  | This module define C<VERSION> and C<DEBUG> package variables. The first one is | 
| 473 |  |  |  |  |  |  | self-explanatory, the second one is 0 by default but can be set to 1 to trace | 
| 474 |  |  |  |  |  |  | all network requests done by this module. Setting it to 2 will also dump the | 
| 475 |  |  |  |  |  |  | requests (and replies) contents. | 
| 476 |  |  |  |  |  |  |  | 
| 477 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 478 |  |  |  |  |  |  |  | 
| 479 |  |  |  |  |  |  | L<WebService::SlimTimer::Task>, L<WebService::SlimTimer::TimeEntry> | 
| 480 |  |  |  |  |  |  |  | 
| 481 |  |  |  |  |  |  | =head1 BUGS | 
| 482 |  |  |  |  |  |  |  | 
| 483 |  |  |  |  |  |  | Currently the C<offset> parameter is not used by C<list_tasks> and | 
| 484 |  |  |  |  |  |  | C<list_entries> and C<list_task_entries> methods, so they are limited to 50 | 
| 485 |  |  |  |  |  |  | tasks for the first one and 5000 entries for the latter two and accessing the | 
| 486 |  |  |  |  |  |  | subsequent results is impossible. | 
| 487 |  |  |  |  |  |  |  | 
| 488 |  |  |  |  |  |  | Access to the comments and tags of the tasks and time entries objects is not | 
| 489 |  |  |  |  |  |  | implemented yet. | 
| 490 |  |  |  |  |  |  |  | 
| 491 |  |  |  |  |  |  | =head1 AUTHOR | 
| 492 |  |  |  |  |  |  |  | 
| 493 |  |  |  |  |  |  | Vadim Zeitlin <vz-cpan@zeitlins.org> | 
| 494 |  |  |  |  |  |  |  | 
| 495 |  |  |  |  |  |  | =head1 COPYRIGHT AND LICENSE | 
| 496 |  |  |  |  |  |  |  | 
| 497 |  |  |  |  |  |  | This software is copyright (c) 2011 by Vadim Zeitlin. | 
| 498 |  |  |  |  |  |  |  | 
| 499 |  |  |  |  |  |  | This is free software; you can redistribute it and/or modify it under | 
| 500 |  |  |  |  |  |  | the same terms as the Perl 5 programming language system itself. | 
| 501 |  |  |  |  |  |  |  | 
| 502 |  |  |  |  |  |  | =cut | 
| 503 |  |  |  |  |  |  |  |