Updates
[infodrom.org/service.infodrom.org] / src / InfoCon / stempel / stempel
1 #! /usr/bin/perl
2
3 #  Time Tracker
4 #  Copyright (c) 2007,8,13,14  Martin Schulze <joey@infodrom.org>
5 #
6 #  This program is free software; you can redistribute it and/or modify
7 #  it under the terms of the GNU General Public License as published by
8 #  the Free Software Foundation; either version 2 of the License, or
9 #  (at your option) any later version.
10 #
11 #  This program is distributed in the hope that it will be useful,
12 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 #  GNU General Public License for more details.
15 #
16 #  You should have received a copy of the GNU General Public License
17 #  along with this program; if not, write to the Free Software
18 #  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
19
20 use strict;
21 use warnings;
22
23 use DBI;
24 use Getopt::Long;
25 use Term::ReadLine;
26
27 my $table = "stempel";
28 my $engine = "dbi:Pg:dbname=infocon";
29
30 my $dbh = DBI->connect($engine);
31 die "Access to database denied!\n" unless $dbh;
32 $dbh->do("SET DateStyle = 'ISO'");
33
34 sub min2hour
35 {
36     my $minutes = shift;
37
38     return sprintf('%02d:%02d', $minutes/60, $minutes%60);
39 }
40
41 sub is_open
42 {
43     my $query = q{SELECT count(*) AS count FROM stempel WHERE stop IS NULL};
44     my $sth = $dbh->prepare ($query);
45     if ($sth && $sth->execute > 0) {
46         my $row = $sth->fetchrow_hashref;
47         return $row->{count} > 0 ? 1 : 0;
48     }
49     return 0;
50 }
51
52 sub customerlist
53 {
54     my @res;
55
56     my $query = q{SELECT DISTINCT customer FROM stempel WHERE start > now() - interval '1 year' ORDER BY customer};
57     my $sth = $dbh->prepare ($query);
58     if ($sth && $sth->execute > 0) {
59         while ((my $row = $sth->fetchrow_hashref)) {
60             push @res, $row->{customer};
61         }
62     }
63     return \@res;
64 }
65
66 sub tasklist
67 {
68     my $customer = shift;
69     my @res;
70
71     my $query = q{SELECT DISTINCT task FROM stempel WHERE start > now() - interval '1 month' AND customer = ? ORDER BY task};
72     my $sth = $dbh->prepare($query);
73     if ($sth && $sth->execute($customer) > 0) {
74         while ((my $row = $sth->fetchrow_hashref)) {
75             push @res, $row->{task};
76         }
77     }
78     return \@res;
79 }
80
81 sub open_task
82 {
83     my $term = new Term::ReadLine '';
84     my $customers = customerlist;
85
86     $term->addhistory($_) foreach (@$customers);
87
88     my $attribs = $term->Attribs;
89     $attribs->{completion_entry_function} = $attribs->{list_completion_function};
90     $attribs->{completion_word} = $customers;
91
92     printf "[%s]\n", join (', ', @$customers);
93
94     my $customer = $term->readline ('Kunde: ');
95
96     if (defined $customer && length($customer) > 0) {
97         $customer =~ s/\s*$//;
98
99         my $tasks = tasklist $customer;
100
101         $term->addhistory($_) foreach (@$tasks);
102         $attribs->{completion_word} = $tasks;
103         while (1) {
104             my $task = $term->readline ('Aufgabe: ');
105             return unless length $task;
106             return if $task eq 'q';
107
108             if ($task eq '?') {
109                 printf "  %s\n", $_ foreach (@$tasks);
110                 next;
111             }
112
113             my $query = q{INSERT INTO stempel (start,customer,task) VALUES('now()',?,?)};
114             my $sth = $dbh->prepare($query);
115             $sth->execute($customer, $task);
116             last;
117         }
118     }
119 }
120
121 sub quarter
122 {
123     my $min = shift;
124
125     return 15 if $min < 15;
126
127     return $min - $min%15 if ($min%15 < 3);
128
129     return $min + 15-$min%15;
130 }
131
132 sub hdiff
133 {
134     my ($sh, $sm, $eh, $em) = @_;
135
136     if ($em >= $sm) {
137         return ($eh-$sh)*60 + $em-$sm;
138     } else {
139         return ($eh-$sh)*60 - ($sm-$em);
140     }
141 }
142
143 sub close_task
144 {
145     my ($d_sec,$d_min,$d_hour,$d_mday,$d_mon,$d_year,$d_wday,$d_isdst) = localtime();
146
147     my $query = q{SELECT oid,start FROM stempel WHERE stop IS NULL};
148     my $sth = $dbh->prepare ($query);
149     if ($sth && $sth->execute > 0) {
150         while ((my $row = $sth->fetchrow_hashref)) {
151             my @arr = split(/ /, $row->{start});
152             my @d = split(/-/, $arr[0]);
153
154             if ($d[0] != $d_year+1900 || $d[1] != $d_mon+1 || $d[2] != $d_mday) {
155                 printf "Task not started today, aborting.\n";
156                 next;
157             }
158
159             my @t = split(/:/, $arr[1]);
160             my $int = quarter(hdiff($t[0], $t[1], $d_hour, $d_min));
161
162             $query = sprintf('UPDATE stempel SET stop=now(),time=%d WHERE oid = %d',
163                              $int, $row->{oid});
164             $dbh->do ($query);
165             printf "Wrote %s.\n", min2hour($int);
166         }
167     }
168     exit(0);
169 }
170
171 sub delete_task
172 {
173     my $query = q{DELETE FROM stempel WHERE stop IS NULL};
174     $dbh->do ($query);
175
176     exit(0);
177 }
178
179 sub list_open
180 {
181     my $exit = shift;
182     my ($d_sec,$d_min,$d_hour,$d_mday,$d_mon,$d_year,$d_wday,$d_isdst) = localtime();
183     my $query = q{SELECT customer,start,task FROM stempel WHERE time IS NULL ORDER BY start,customer};
184
185     my $sth = $dbh->prepare ($query);
186     if ($sth && $sth->execute > 0) {
187         while ((my $row = $sth->fetchrow_hashref)) {
188             my @arr = split(/ /, $row->{start});
189             my $day = $arr[0];
190             my @d = split(/-/, $day);
191
192             if ($d[0] != $d_year+1900 || $d[1] != $d_mon+1 || $d[2] != $d_mday) {
193                 printf "Task not started today.\n";
194                 next if $exit eq 1;
195             }
196
197             my @t = split(/:/, $arr[1]);
198             my $time = sprintf('%02d:%02d', $t[0], $t[1]);
199             my $int = quarter(hdiff($t[0], $t[1], $d_hour, $d_min));
200
201             printf "%-15s  %s %s (%s)  %s\n", $row->{customer}, $day, $time, min2hour($int), $row->{task};
202         }
203     }
204     exit 0 if $exit;
205 }
206
207 sub list
208 {
209     my $month = shift;
210     my ($d_sec,$d_min,$d_hour,$d_mday,$d_mon,$d_year,$d_wday,$d_isdst) = localtime();
211     my $query = q{SELECT start,customer,time,task FROM stempel WHERE time IS NOT NULL };
212     
213     if ($month =~ /^(\d{4})-?(\d\d?)$/) {
214         my $pivot = $1 . '-' . $2 . '-01';
215         $query .= "AND start >= '$pivot' AND start <= '$pivot'::date + interval '1 month' ";
216     } elsif ($month =~ /^(\d{4})$/) {
217         my $pivot = $1 . '-01-01';
218         $query .= "AND start >= '$pivot' AND start <= '$pivot'::date + interval '1 year' ";
219     } elsif ($month =~ /^(\d\d?)$/) {
220         my $pivot = $d_year+1900 . '-' . $1 . '-01';
221         $query .= "AND start >= '$pivot' AND start <= '$pivot'::date + interval '1 month' ";
222     } elsif ($month !~ /^all$/) {
223         my $pivot = sprintf('%04d-%02d-01', $d_year+1900, $d_mon+1);
224         $query .= "AND start >= '$pivot' AND start <= '$pivot'::date + interval '1 month' ";
225     }
226     $query .= "ORDER BY customer,start";
227
228     my $sth = $dbh->prepare ($query);
229
230     if ($sth && $sth->execute > 0) {
231         while ((my $row = $sth->fetchrow_hashref)) {
232             if (defined $row->{time}) {
233                 my $day = (split(/ /, $row->{start}))[0];
234                 my $time = min2hour $row->{time};
235                 printf "%-15s  %s  %s  %s\n", $row->{customer}, $day, $time, $row->{task};
236             }
237         }
238     }
239     exit 0;
240 }
241
242 sub toggle_task
243 {
244     if (is_open) {
245         list_open 1;
246     } else {
247         open_task;
248     }
249 }
250
251 sub late_close_task
252 {
253     my $delta = shift;
254     my ($d_sec,$d_min,$d_hour,$d_mday,$d_mon,$d_year,$d_wday,$d_isdst) = localtime();
255
256     return unless is_open;
257
258     my $query = sprintf("UPDATE stempel SET stop=start + interval '%d minutes',time=%d WHERE stop IS NULL",
259                         $delta, quarter($delta));
260     $dbh->do($query);
261 }
262
263 sub move_task
264 {
265     my $minutes = shift;
266
267     return unless is_open;
268
269     my $query = sprintf("UPDATE stempel SET start = start - interval '%d minutes' WHERE stop IS NULL", $minutes);
270     $dbh->do($query);
271 }
272
273 sub alter_task
274 {
275     return unless is_open;
276
277     list_open;
278
279     my $term = new Term::ReadLine '';
280
281     my $task = $term->readline ('Aufgabe: ');
282
283     if (length($task) > 0) {
284         my $query = q{UPDATE stempel SET task=? WHERE stop IS NULL};
285         my $sth = $dbh->prepare($query);
286         $sth->execute($task);
287     }
288     exit 0;
289 }
290
291 sub help
292 {
293     print <<"END";
294 stempel  Copyright (c) 2007,8  Martin Schulze <joey\@infodrom.org>
295     --list [month] list month [default=current|all]
296     --back n       move start of current task back by n minutes
297     --open         list open
298     --close time   close open task
299     --help         this text
300     --end|-d       terminate task
301     --delete       delete open task
302     --task         alter task content
303 END
304     exit;
305 }
306
307 my $opt_close = undef;
308 my $opt_list = undef;
309 my $opt_back = undef;
310 my %options = ('list:s' => \$opt_list,
311                'back=i' => \$opt_back,
312                'open' => \&list_open,
313                'help' => \&help,
314                'close=i' => \$opt_close,
315                'delete' => \&delete_task,
316                'task|t' => \&alter_task,
317                'terminate|end|d' => \&close_task,
318                );
319 GetOptions %options;
320
321 if ($opt_close) {
322     late_close_task $opt_close;
323 } elsif (defined $opt_list) {
324     list $opt_list;
325 } elsif (defined $opt_back) {
326     move_task $opt_back;
327 } else {
328     toggle_task;
329 }