openall_time_applet 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/classes/connection.rb +40 -6
- data/conf/db_schema.rb +24 -1
- data/glade/win_overview.glade +1 -0
- data/glade/win_preferences.glade +1 -2
- data/glade/win_timelog_edit.glade +77 -4
- data/glade/win_worktime_overview.glade +110 -0
- data/gui/trayicon.rb +67 -14
- data/gui/win_overview.rb +18 -7
- data/gui/win_preferences.rb +15 -3
- data/gui/win_timelog_edit.rb +68 -2
- data/gui/win_worktime_overview.rb +76 -0
- data/lib/openall_time_applet.rb +112 -4
- data/models/task.rb +26 -0
- data/models/timelog.rb +65 -0
- data/models/worktime.rb +39 -0
- data/openall_time_applet.gemspec +6 -2
- metadata +7 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.2
|
data/classes/connection.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require "socket"
|
2
1
|
require "json"
|
3
2
|
|
4
3
|
#This class handels various operations with the Openall-installation. It uses HTTP and JSON.
|
@@ -20,23 +19,58 @@ class Openall_time_applet::Connection
|
|
20
19
|
def login
|
21
20
|
#For some weird reason OpenAll seems to only accept multipart-post-requests??
|
22
21
|
@http.post_multipart("index.php?c=Auth&m=validateLogin", {"username" => @args[:username], "password" => @args[:password]})
|
23
|
-
@http.reconnect
|
24
22
|
|
25
23
|
#Verify login by reading dashboard HTML.
|
24
|
+
@http.reconnect
|
26
25
|
res = @http.get("index.php?c=Dashboard")
|
27
26
|
raise _("Could not log in.") if !res.body.match(/<ul id="webticker" >/)
|
28
27
|
end
|
29
28
|
|
30
29
|
def request(args)
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
@http.reconnect
|
31
|
+
|
32
|
+
#Possible to give a string instead of hash to do it simple.
|
33
|
+
args = {:url => "?c=Jsonapi&m=#{args}"} if args.is_a?(String) or args.is_a?(Symbol)
|
34
|
+
args[:url] = "?c=Jsonapi&m=#{args[:method]}" if args[:method] and !args[:url]
|
35
|
+
|
36
|
+
#Send request to OpenAll via HTTP.
|
37
|
+
if args[:post]
|
38
|
+
res = @http.post_multipart(args[:url], args[:post])
|
39
|
+
else
|
40
|
+
res = @http.get(args[:url])
|
41
|
+
end
|
42
|
+
|
43
|
+
raise _("Empty body returned from OpenAll.") if res.body.to_s.strip.length <= 0
|
44
|
+
|
45
|
+
#Parse result as JSON.
|
46
|
+
begin
|
47
|
+
parsed = JSON.parse(res.body)
|
48
|
+
rescue
|
49
|
+
raise sprintf(_("Could not parse JSON from: %s"), res.body)
|
50
|
+
end
|
51
|
+
|
52
|
+
#An error occurred in OpenAll. Make it look like an error here as well.
|
53
|
+
if parsed.is_a?(Hash) and parsed["type"] == "error"
|
54
|
+
#Hack the backtrace to include code-lines from PHP.
|
55
|
+
begin
|
56
|
+
raise "(PHP-#{parsed["class"]}) #{parsed["msg"]}"
|
57
|
+
rescue => e
|
58
|
+
newbt = parsed["bt"]
|
59
|
+
e.backtrace.each do |bt|
|
60
|
+
newbt << bt
|
61
|
+
end
|
62
|
+
|
63
|
+
e.set_backtrace(newbt)
|
64
|
+
|
65
|
+
raise e
|
66
|
+
end
|
67
|
+
end
|
34
68
|
|
35
69
|
return parsed
|
36
70
|
end
|
37
71
|
|
38
72
|
def task_list
|
39
|
-
return self.request("
|
73
|
+
return self.request("getAllTasksForUser")
|
40
74
|
end
|
41
75
|
|
42
76
|
def destroy
|
data/conf/db_schema.rb
CHANGED
@@ -7,10 +7,21 @@ Openall_time_applet::DB_SCHEMA = {
|
|
7
7
|
{"name" => "value", "type" => "text"}
|
8
8
|
]
|
9
9
|
},
|
10
|
+
"Task" => {
|
11
|
+
"columns" => [
|
12
|
+
{"name" => "id", "type" => "int", "autoincr" => true, "primarykey" => true},
|
13
|
+
{"name" => "openall_uid", "type" => "int"},
|
14
|
+
{"name" => "title", "type" => "varchar"}
|
15
|
+
],
|
16
|
+
"indexes" => [
|
17
|
+
"openall_uid"
|
18
|
+
]
|
19
|
+
},
|
10
20
|
"Timelog" => {
|
11
21
|
"columns" => [
|
12
22
|
{"name" => "id", "type" => "int", "autoincr" => true, "primarykey" => true},
|
13
23
|
{"name" => "openall_uid", "type" => "int"},
|
24
|
+
{"name" => "task_id", "type" => "int"},
|
14
25
|
{"name" => "time", "type" => "int"},
|
15
26
|
{"name" => "time_transport", "type" => "int"},
|
16
27
|
{"name" => "descr", "type" => "text"},
|
@@ -18,7 +29,19 @@ Openall_time_applet::DB_SCHEMA = {
|
|
18
29
|
{"name" => "sync_last", "type" => "datetime"}
|
19
30
|
],
|
20
31
|
"indexes" => [
|
21
|
-
"openall_uid"
|
32
|
+
"openall_uid",
|
33
|
+
"task_id"
|
34
|
+
]
|
35
|
+
},
|
36
|
+
"Worktime" => {
|
37
|
+
"columns" => [
|
38
|
+
{"name" => "id", "type" => "int", "autoincr" => true, "primarykey" => true},
|
39
|
+
{"name" => "openall_uid", "type" => "int"},
|
40
|
+
{"name" => "task_id", "type" => "int"},
|
41
|
+
{"name" => "timestamp", "type" => "datetime"},
|
42
|
+
{"name" => "worktime", "type" => "int"},
|
43
|
+
{"name" => "transporttime", "type" => "int"},
|
44
|
+
{"name" => "comment", "type" => "text"}
|
22
45
|
]
|
23
46
|
}
|
24
47
|
}
|
data/glade/win_overview.glade
CHANGED
@@ -8,6 +8,7 @@
|
|
8
8
|
<property name="window_position">center</property>
|
9
9
|
<property name="default_width">640</property>
|
10
10
|
<property name="default_height">480</property>
|
11
|
+
<signal name="destroy" handler="on_window_destroy" swapped="no"/>
|
11
12
|
<child>
|
12
13
|
<object class="GtkVBox" id="vbox1">
|
13
14
|
<property name="visible">True</property>
|
data/glade/win_preferences.glade
CHANGED
@@ -6,8 +6,7 @@
|
|
6
6
|
<property name="can_focus">False</property>
|
7
7
|
<property name="title" translatable="yes">Preferences</property>
|
8
8
|
<property name="window_position">center</property>
|
9
|
-
<property name="default_width">
|
10
|
-
<property name="default_height">480</property>
|
9
|
+
<property name="default_width">400</property>
|
11
10
|
<child>
|
12
11
|
<object class="GtkVBox" id="vbox1">
|
13
12
|
<property name="visible">True</property>
|
@@ -6,8 +6,7 @@
|
|
6
6
|
<property name="can_focus">False</property>
|
7
7
|
<property name="title" translatable="yes">Timelog</property>
|
8
8
|
<property name="window_position">center</property>
|
9
|
-
<property name="default_width">
|
10
|
-
<property name="default_height">480</property>
|
9
|
+
<property name="default_width">360</property>
|
11
10
|
<child>
|
12
11
|
<object class="GtkVBox" id="vbox1">
|
13
12
|
<property name="visible">True</property>
|
@@ -27,7 +26,7 @@
|
|
27
26
|
<object class="GtkTable" id="table1">
|
28
27
|
<property name="visible">True</property>
|
29
28
|
<property name="can_focus">False</property>
|
30
|
-
<property name="n_rows">
|
29
|
+
<property name="n_rows">6</property>
|
31
30
|
<property name="n_columns">2</property>
|
32
31
|
<property name="column_spacing">3</property>
|
33
32
|
<property name="row_spacing">3</property>
|
@@ -120,6 +119,64 @@
|
|
120
119
|
<property name="bottom_attach">3</property>
|
121
120
|
</packing>
|
122
121
|
</child>
|
122
|
+
<child>
|
123
|
+
<object class="GtkLabel" id="label6">
|
124
|
+
<property name="visible">True</property>
|
125
|
+
<property name="can_focus">False</property>
|
126
|
+
<property name="xalign">0</property>
|
127
|
+
<property name="label" translatable="yes">Task</property>
|
128
|
+
</object>
|
129
|
+
<packing>
|
130
|
+
<property name="top_attach">3</property>
|
131
|
+
<property name="bottom_attach">4</property>
|
132
|
+
<property name="x_options">GTK_FILL</property>
|
133
|
+
<property name="y_options">GTK_FILL</property>
|
134
|
+
</packing>
|
135
|
+
</child>
|
136
|
+
<child>
|
137
|
+
<object class="GtkComboBox" id="cbTask">
|
138
|
+
<property name="visible">True</property>
|
139
|
+
<property name="can_focus">False</property>
|
140
|
+
</object>
|
141
|
+
<packing>
|
142
|
+
<property name="left_attach">1</property>
|
143
|
+
<property name="right_attach">2</property>
|
144
|
+
<property name="top_attach">3</property>
|
145
|
+
<property name="bottom_attach">4</property>
|
146
|
+
</packing>
|
147
|
+
</child>
|
148
|
+
<child>
|
149
|
+
<object class="GtkCheckButton" id="cbShouldSync">
|
150
|
+
<property name="label" translatable="yes">Should sync</property>
|
151
|
+
<property name="visible">True</property>
|
152
|
+
<property name="can_focus">True</property>
|
153
|
+
<property name="receives_default">False</property>
|
154
|
+
<property name="use_action_appearance">False</property>
|
155
|
+
<property name="draw_indicator">True</property>
|
156
|
+
</object>
|
157
|
+
<packing>
|
158
|
+
<property name="right_attach">2</property>
|
159
|
+
<property name="top_attach">4</property>
|
160
|
+
<property name="bottom_attach">5</property>
|
161
|
+
<property name="x_options">GTK_FILL</property>
|
162
|
+
<property name="y_options">GTK_FILL</property>
|
163
|
+
</packing>
|
164
|
+
</child>
|
165
|
+
<child>
|
166
|
+
<object class="GtkCheckButton" id="cbStartTracking">
|
167
|
+
<property name="label" translatable="yes">Start tracking after saving</property>
|
168
|
+
<property name="visible">True</property>
|
169
|
+
<property name="can_focus">True</property>
|
170
|
+
<property name="receives_default">False</property>
|
171
|
+
<property name="use_action_appearance">False</property>
|
172
|
+
<property name="draw_indicator">True</property>
|
173
|
+
</object>
|
174
|
+
<packing>
|
175
|
+
<property name="right_attach">2</property>
|
176
|
+
<property name="top_attach">5</property>
|
177
|
+
<property name="bottom_attach">6</property>
|
178
|
+
</packing>
|
179
|
+
</child>
|
123
180
|
</object>
|
124
181
|
</child>
|
125
182
|
</object>
|
@@ -145,6 +202,22 @@
|
|
145
202
|
<property name="can_focus">False</property>
|
146
203
|
<property name="spacing">3</property>
|
147
204
|
<property name="layout_style">end</property>
|
205
|
+
<child>
|
206
|
+
<object class="GtkButton" id="btnRemove">
|
207
|
+
<property name="label">gtk-remove</property>
|
208
|
+
<property name="visible">True</property>
|
209
|
+
<property name="can_focus">True</property>
|
210
|
+
<property name="receives_default">True</property>
|
211
|
+
<property name="use_action_appearance">False</property>
|
212
|
+
<property name="use_stock">True</property>
|
213
|
+
<signal name="clicked" handler="on_btnRemove_clicked" swapped="no"/>
|
214
|
+
</object>
|
215
|
+
<packing>
|
216
|
+
<property name="expand">False</property>
|
217
|
+
<property name="fill">False</property>
|
218
|
+
<property name="position">0</property>
|
219
|
+
</packing>
|
220
|
+
</child>
|
148
221
|
<child>
|
149
222
|
<object class="GtkButton" id="btnSave">
|
150
223
|
<property name="label">gtk-save</property>
|
@@ -158,7 +231,7 @@
|
|
158
231
|
<packing>
|
159
232
|
<property name="expand">False</property>
|
160
233
|
<property name="fill">False</property>
|
161
|
-
<property name="position">
|
234
|
+
<property name="position">1</property>
|
162
235
|
</packing>
|
163
236
|
</child>
|
164
237
|
</object>
|
@@ -0,0 +1,110 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<interface>
|
3
|
+
<requires lib="gtk+" version="2.24"/>
|
4
|
+
<!-- interface-naming-policy project-wide -->
|
5
|
+
<object class="GtkWindow" id="window">
|
6
|
+
<property name="can_focus">False</property>
|
7
|
+
<property name="title" translatable="yes">Week status</property>
|
8
|
+
<property name="window_position">center</property>
|
9
|
+
<property name="default_width">440</property>
|
10
|
+
<child>
|
11
|
+
<object class="GtkVBox" id="vbox1">
|
12
|
+
<property name="visible">True</property>
|
13
|
+
<property name="can_focus">False</property>
|
14
|
+
<child>
|
15
|
+
<object class="GtkHBox" id="hbox1">
|
16
|
+
<property name="visible">True</property>
|
17
|
+
<property name="can_focus">False</property>
|
18
|
+
<child>
|
19
|
+
<object class="GtkHButtonBox" id="hbuttonbox1">
|
20
|
+
<property name="visible">True</property>
|
21
|
+
<property name="can_focus">False</property>
|
22
|
+
<property name="layout_style">start</property>
|
23
|
+
<child>
|
24
|
+
<object class="GtkButton" id="btnPrevious">
|
25
|
+
<property name="label">gtk-media-previous</property>
|
26
|
+
<property name="visible">True</property>
|
27
|
+
<property name="can_focus">True</property>
|
28
|
+
<property name="receives_default">True</property>
|
29
|
+
<property name="use_action_appearance">False</property>
|
30
|
+
<property name="use_stock">True</property>
|
31
|
+
<signal name="clicked" handler="on_btnPrevious_clicked" swapped="no"/>
|
32
|
+
</object>
|
33
|
+
<packing>
|
34
|
+
<property name="expand">False</property>
|
35
|
+
<property name="fill">False</property>
|
36
|
+
<property name="position">0</property>
|
37
|
+
</packing>
|
38
|
+
</child>
|
39
|
+
</object>
|
40
|
+
<packing>
|
41
|
+
<property name="expand">True</property>
|
42
|
+
<property name="fill">True</property>
|
43
|
+
<property name="position">0</property>
|
44
|
+
</packing>
|
45
|
+
</child>
|
46
|
+
<child>
|
47
|
+
<object class="GtkLabel" id="label2">
|
48
|
+
<property name="visible">True</property>
|
49
|
+
<property name="can_focus">False</property>
|
50
|
+
<property name="label" translatable="yes">Week</property>
|
51
|
+
</object>
|
52
|
+
<packing>
|
53
|
+
<property name="expand">True</property>
|
54
|
+
<property name="fill">True</property>
|
55
|
+
<property name="position">1</property>
|
56
|
+
</packing>
|
57
|
+
</child>
|
58
|
+
<child>
|
59
|
+
<object class="GtkHButtonBox" id="hbuttonbox2">
|
60
|
+
<property name="visible">True</property>
|
61
|
+
<property name="can_focus">False</property>
|
62
|
+
<property name="layout_style">end</property>
|
63
|
+
<child>
|
64
|
+
<object class="GtkButton" id="btnNext">
|
65
|
+
<property name="label">gtk-media-next</property>
|
66
|
+
<property name="visible">True</property>
|
67
|
+
<property name="can_focus">True</property>
|
68
|
+
<property name="receives_default">True</property>
|
69
|
+
<property name="use_action_appearance">False</property>
|
70
|
+
<property name="use_stock">True</property>
|
71
|
+
<signal name="clicked" handler="on_btnNext_clicked" swapped="no"/>
|
72
|
+
</object>
|
73
|
+
<packing>
|
74
|
+
<property name="expand">False</property>
|
75
|
+
<property name="fill">False</property>
|
76
|
+
<property name="position">0</property>
|
77
|
+
</packing>
|
78
|
+
</child>
|
79
|
+
</object>
|
80
|
+
<packing>
|
81
|
+
<property name="expand">True</property>
|
82
|
+
<property name="fill">True</property>
|
83
|
+
<property name="position">2</property>
|
84
|
+
</packing>
|
85
|
+
</child>
|
86
|
+
</object>
|
87
|
+
<packing>
|
88
|
+
<property name="expand">False</property>
|
89
|
+
<property name="fill">True</property>
|
90
|
+
<property name="position">0</property>
|
91
|
+
</packing>
|
92
|
+
</child>
|
93
|
+
<child>
|
94
|
+
<object class="GtkHBox" id="boxContent">
|
95
|
+
<property name="visible">True</property>
|
96
|
+
<property name="can_focus">False</property>
|
97
|
+
<child>
|
98
|
+
<placeholder/>
|
99
|
+
</child>
|
100
|
+
</object>
|
101
|
+
<packing>
|
102
|
+
<property name="expand">True</property>
|
103
|
+
<property name="fill">True</property>
|
104
|
+
<property name="position">1</property>
|
105
|
+
</packing>
|
106
|
+
</child>
|
107
|
+
</object>
|
108
|
+
</child>
|
109
|
+
</object>
|
110
|
+
</interface>
|
data/gui/trayicon.rb
CHANGED
@@ -7,7 +7,9 @@ class Openall_time_applet::Gui::Trayicon
|
|
7
7
|
@ti = Gtk::StatusIcon.new
|
8
8
|
@ti.file = "../gfx/icon_time.png"
|
9
9
|
@ti.signal_connect("popup-menu", &self.method(:on_statusicon_rightclick))
|
10
|
-
|
10
|
+
end
|
11
|
+
|
12
|
+
def on_statusicon_rightclick(tray, button, time)
|
11
13
|
#Build rightclick-menu for tray-icon.
|
12
14
|
timelog_new = Gtk::ImageMenuItem.new(Gtk::Stock::NEW)
|
13
15
|
timelog_new.label = _("New timelog")
|
@@ -17,24 +19,63 @@ class Openall_time_applet::Gui::Trayicon
|
|
17
19
|
overview.label = _("Overview")
|
18
20
|
overview.signal_connect("activate", &self.method(:on_overview_activate))
|
19
21
|
|
22
|
+
worktime_overview = Gtk::ImageMenuItem.new(Gtk::Stock::HOME)
|
23
|
+
worktime_overview.label = _("Time overview")
|
24
|
+
worktime_overview.signal_connect("activate", &self.method(:on_worktimeOverview_activate))
|
25
|
+
|
20
26
|
pref = Gtk::ImageMenuItem.new(Gtk::Stock::PREFERENCES)
|
21
27
|
pref.signal_connect("activate", &self.method(:on_preferences_activate))
|
22
28
|
|
23
29
|
quit = Gtk::ImageMenuItem.new(Gtk::Stock::QUIT)
|
24
30
|
quit.signal_connect("activate", &self.method(:on_quit_activate))
|
25
31
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
32
|
+
sync = Gtk::ImageMenuItem.new(Gtk::Stock::HARDDISK)
|
33
|
+
sync.label = _("Synchronize with OpenAll")
|
34
|
+
sync.signal_connect("activate", &self.method(:on_sync_activate))
|
35
|
+
|
36
|
+
menu = Gtk::Menu.new
|
37
|
+
menu.append(timelog_new)
|
38
|
+
menu.append(overview)
|
39
|
+
menu.append(worktime_overview)
|
40
|
+
menu.append(Gtk::SeparatorMenuItem.new)
|
41
|
+
menu.append(pref)
|
42
|
+
menu.append(Gtk::SeparatorMenuItem.new)
|
43
|
+
|
44
|
+
#Make a list of all timelogs in the menu.
|
45
|
+
@args[:oata].ob.list(:Timelog, {"orderby" => "id"}) do |timelog|
|
46
|
+
label = sprintf(_("Track: %s"), timelog.descr_short)
|
47
|
+
|
48
|
+
#If this is the active timelog, make the label bold, by getting the label-child and using HTML-markup on it.
|
49
|
+
if @args[:oata].timelog_active and @args[:oata].timelog_active.id == timelog.id
|
50
|
+
mi = Gtk::ImageMenuItem.new(Gtk::Stock::MEDIA_RECORD)
|
51
|
+
mi.children[0].markup = "<b>#{label}</b>"
|
52
|
+
mi.signal_connect("activate", &self.method(:on_stopTracking_activate))
|
53
|
+
else
|
54
|
+
mi = Gtk::MenuItem.new(label)
|
55
|
+
#Change the active timelog, when the timelog is clicked.
|
56
|
+
mi.signal_connect("activate") do
|
57
|
+
@args[:oata].timelog_active = timelog
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
menu.append(mi)
|
62
|
+
end
|
63
|
+
|
64
|
+
if @args[:oata].timelog_active
|
65
|
+
menu.append(Gtk::SeparatorMenuItem.new)
|
66
|
+
|
67
|
+
#If tracking is active, then show how many seconds has been tracked until now in menu as an item.
|
68
|
+
secs = Time.now.to_i - @args[:oata].timelog_active_time.to_i
|
69
|
+
label = Gtk::MenuItem.new(sprintf(_("%s seconds"), secs))
|
70
|
+
menu.append(label)
|
71
|
+
end
|
72
|
+
|
73
|
+
menu.append(Gtk::SeparatorMenuItem.new)
|
74
|
+
menu.append(sync)
|
75
|
+
menu.append(quit)
|
76
|
+
menu.show_all
|
77
|
+
|
78
|
+
menu.popup(nil, nil, button, time)
|
38
79
|
end
|
39
80
|
|
40
81
|
def on_preferences_activate(*args)
|
@@ -49,7 +90,19 @@ class Openall_time_applet::Gui::Trayicon
|
|
49
90
|
@args[:oata].show_overview
|
50
91
|
end
|
51
92
|
|
93
|
+
def on_worktimeOverview_activate(*args)
|
94
|
+
@args[:oata].show_worktime_overview
|
95
|
+
end
|
96
|
+
|
52
97
|
def on_quit_activate(*args)
|
53
|
-
|
98
|
+
@args[:oata].destroy
|
99
|
+
end
|
100
|
+
|
101
|
+
def on_sync_activate(*args)
|
102
|
+
@args[:oata].sync
|
103
|
+
end
|
104
|
+
|
105
|
+
def on_stopTracking_activate(*args)
|
106
|
+
@args[:oata].timelog_stop_tracking
|
54
107
|
end
|
55
108
|
end
|
data/gui/win_overview.rb
CHANGED
@@ -8,21 +8,27 @@ class Openall_time_applet::Gui::Win_overview
|
|
8
8
|
@gui.translate
|
9
9
|
@gui.connect_signals{|h| method(h)}
|
10
10
|
|
11
|
-
@gui["tvTimelogs"].init([_("ID"), _("Description"), _("Time"), _("Transport"), _("Needs sync")])
|
11
|
+
@gui["tvTimelogs"].init([_("ID"), _("Description"), _("Time"), _("Transport"), _("Needs sync"), _("Task")])
|
12
12
|
@gui["tvTimelogs"].columns[0].visible = false
|
13
13
|
self.reload_timelogs
|
14
14
|
|
15
|
+
#Reload the treeview if something happened to a timelog.
|
16
|
+
@reload_id = @args[:oata].ob.connect("object" => :Timelog, "signals" => ["add", "update", "delete"], &self.method(:reload_timelogs))
|
17
|
+
|
15
18
|
@gui["window"].show_all
|
16
19
|
end
|
17
20
|
|
18
21
|
def reload_timelogs
|
22
|
+
@gui["tvTimelogs"].model.clear
|
19
23
|
@args[:oata].ob.list(:Timelog, {"orderby" => "id"}) do |timelog|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
@gui["tvTimelogs"].append([
|
25
|
+
timelog.id,
|
26
|
+
timelog.descr_short,
|
27
|
+
timelog.time_as_human,
|
28
|
+
timelog.time_transport_as_human,
|
29
|
+
Knj::Strings.yn_str(timelog[:sync_need], _("Yes"), _("No")),
|
30
|
+
timelog.task_name
|
31
|
+
])
|
26
32
|
end
|
27
33
|
end
|
28
34
|
|
@@ -35,4 +41,9 @@ class Openall_time_applet::Gui::Win_overview
|
|
35
41
|
win_timelog_edit.gui["window"].modal = @gui["window"]
|
36
42
|
win_timelog_edit.gui["window"].transient_for = @gui["window"]
|
37
43
|
end
|
44
|
+
|
45
|
+
def on_window_destroy
|
46
|
+
#Unconnect reload-event. Else it will crash on call to destroyed object. Also frees up various ressources.
|
47
|
+
@args[:oata].ob.unconnect("object" => :Timelog, "conn_id" => @reload_id)
|
48
|
+
end
|
38
49
|
end
|
data/gui/win_preferences.rb
CHANGED
@@ -13,6 +13,7 @@ class Openall_time_applet::Gui::Win_preferences
|
|
13
13
|
@gui["window"].show_all
|
14
14
|
end
|
15
15
|
|
16
|
+
#Loads values from database into widgets.
|
16
17
|
def load_values
|
17
18
|
@gui["txtHost"].text = Knj::Opts.get("openall_host")
|
18
19
|
@gui["txtPort"].text = Knj::Opts.get("openall_port")
|
@@ -26,6 +27,7 @@ class Openall_time_applet::Gui::Win_preferences
|
|
26
27
|
end
|
27
28
|
end
|
28
29
|
|
30
|
+
#Saves values from widgets into database.
|
29
31
|
def on_btnSave_clicked
|
30
32
|
Knj::Opts.set("openall_host", @gui["txtHost"].text)
|
31
33
|
Knj::Opts.set("openall_port", @gui["txtPort"].text)
|
@@ -35,12 +37,15 @@ class Openall_time_applet::Gui::Win_preferences
|
|
35
37
|
@gui["window"].destroy
|
36
38
|
end
|
37
39
|
|
40
|
+
#Tries to connect to OpenAll with the given information and receive a task-list as well to validate information and connectivity.
|
38
41
|
def on_btnTest_clicked
|
39
42
|
ws = Knj::Gtk2::StatusWindow.new("transient_for" => @gui["window"])
|
40
43
|
ws.label = _("Connecting and logging in...")
|
41
44
|
|
45
|
+
#Do the stuff in thread so GUI wont lock.
|
42
46
|
Knj::Thread.new do
|
43
47
|
begin
|
48
|
+
#Connect to OpenAll, log in, get a list of tasks to test the information and connection.
|
44
49
|
@args[:oata].oa_conn do |conn|
|
45
50
|
ws.percent = 0.3
|
46
51
|
ws.label = _("Getting task-list.")
|
@@ -50,12 +55,19 @@ class Openall_time_applet::Gui::Win_preferences
|
|
50
55
|
ws.percent = 1
|
51
56
|
end
|
52
57
|
rescue => e
|
53
|
-
|
54
|
-
Knj::Gtk2.msgbox(
|
58
|
+
#Show error for user if error occurrs.
|
59
|
+
Knj::Gtk2.msgbox(
|
60
|
+
"msg" => Knj::Errors.error_str(e),
|
61
|
+
"type" => "warning",
|
62
|
+
"title" => _("Error"),
|
63
|
+
"run" => false,
|
64
|
+
"transient_for" => @gui["window"]
|
65
|
+
)
|
55
66
|
ensure
|
67
|
+
#Be sure that the status-window will be closed.
|
56
68
|
Knj::Thread.new do
|
57
69
|
sleep 1.5
|
58
|
-
ws.destroy
|
70
|
+
ws.destroy if ws
|
59
71
|
end
|
60
72
|
end
|
61
73
|
end
|
data/gui/win_timelog_edit.rb
CHANGED
@@ -8,22 +8,88 @@ class Openall_time_applet::Gui::Win_timelog_edit
|
|
8
8
|
@gui.translate
|
9
9
|
@gui.connect_signals{|h| method(h)}
|
10
10
|
|
11
|
+
tasks_opts = [_("None")] + @args[:oata].ob.list(:Task, {"orderby" => "openall_uid"})
|
12
|
+
@gui["cbTask"].init(tasks_opts)
|
13
|
+
|
14
|
+
#We are editting a timelog - set widget-values.
|
15
|
+
@timelog = @args[:timelog]
|
16
|
+
|
17
|
+
if @timelog
|
18
|
+
@gui["txtDescr"].text = @timelog[:descr]
|
19
|
+
@gui["txtTime"].text = @timelog.time_as_human
|
20
|
+
@gui["txtTimeTransport"].text = @timelog.time_transport_as_human
|
21
|
+
@gui["cbTask"].sel = @timelog.task if @timelog.task
|
22
|
+
@gui["cbShouldSync"].active = Knj::Strings.yn_str(@timelog[:sync_need], true, false)
|
23
|
+
else
|
24
|
+
@gui["btnRemove"].visible = false
|
25
|
+
end
|
26
|
+
|
27
|
+
#Show the window.
|
11
28
|
@gui["window"].show_all
|
12
29
|
end
|
13
30
|
|
14
31
|
def on_btnSave_clicked(*args)
|
32
|
+
#Generate task-ID based on widget-value.
|
33
|
+
task = @gui["cbTask"].sel
|
34
|
+
if task
|
35
|
+
task_id = task.id
|
36
|
+
else
|
37
|
+
task_id = 0
|
38
|
+
end
|
39
|
+
|
40
|
+
#Get times as integers based on widget-values.
|
41
|
+
if @gui["txtTime"].text == ""
|
42
|
+
time_secs = 0
|
43
|
+
else
|
44
|
+
begin
|
45
|
+
time_secs = Knj::Strings.human_time_str_to_secs(@gui["txtTime"].text)
|
46
|
+
rescue => e
|
47
|
+
Knj::Gtk2.msgbox(_("You have entered an invalid time-format.") + "\n\n" + Knj::Errors.error_str(e))
|
48
|
+
return nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if @gui["txtTimeTransport"].text == ""
|
53
|
+
time_transport_secs = 0
|
54
|
+
else
|
55
|
+
begin
|
56
|
+
time_transport_secs = Knj::Strings.human_time_str_to_secs(@gui["txtTimeTransport"].text)
|
57
|
+
rescue => e
|
58
|
+
Knj::Gtk2.msgbox(_("You have entered an invalid transport-time-format.") + "\n\n" + Knj::Errors.error_str(e))
|
59
|
+
return nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
#Generate hash for updating dataabase.
|
15
64
|
save_hash = {
|
16
65
|
:descr => @gui["txtDescr"].text,
|
17
|
-
:time =>
|
18
|
-
:time_transport =>
|
66
|
+
:time => time_secs,
|
67
|
+
:time_transport => time_transport_secs,
|
68
|
+
:task_id => task_id,
|
69
|
+
:sync_need => Knj::Strings.yn_str(@gui["cbShouldSync"].active?, 1, 0)
|
19
70
|
}
|
20
71
|
|
72
|
+
#Update or add the timelog.
|
21
73
|
if @timelog
|
22
74
|
@timelog.update(save_hash)
|
23
75
|
else
|
24
76
|
@timelog = @args[:oata].ob.add(:Timelog, save_hash)
|
25
77
|
end
|
26
78
|
|
79
|
+
#Start tracking the current timelog if the checkbox has been checked.
|
80
|
+
if @gui["cbStartTracking"].active?
|
81
|
+
@args[:oata].timelog_active = @timelog
|
82
|
+
end
|
83
|
+
|
84
|
+
@gui["window"].destroy
|
85
|
+
end
|
86
|
+
|
87
|
+
def on_btnRemove_clicked(*args)
|
88
|
+
if Knj::Gtk2.msgbox(_("Do you want to remove this timelog? This will not delete the timelog on OpenAll."), "yesno") != "yes"
|
89
|
+
return nil
|
90
|
+
end
|
91
|
+
|
92
|
+
@args[:oata].ob.delete(@timelog)
|
27
93
|
@gui["window"].destroy
|
28
94
|
end
|
29
95
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
class Openall_time_applet::Gui::Win_worktime_overview
|
2
|
+
def initialize(args)
|
3
|
+
@args = args
|
4
|
+
|
5
|
+
@gui = Gtk::Builder.new.add("../glade/win_worktime_overview.glade")
|
6
|
+
@gui.translate
|
7
|
+
@gui.connect_signals{|h| method(h)}
|
8
|
+
|
9
|
+
self.build_week(Knj::Datet.new)
|
10
|
+
|
11
|
+
@gui["window"].show_all
|
12
|
+
end
|
13
|
+
|
14
|
+
def build_week(date)
|
15
|
+
stats = {
|
16
|
+
:task_total => {},
|
17
|
+
:days_total => {}
|
18
|
+
}
|
19
|
+
|
20
|
+
@args[:oata].ob.list(:Worktime, {"timestamp_month" => date}) do |wt|
|
21
|
+
task = wt.task
|
22
|
+
date = wt.timestamp
|
23
|
+
|
24
|
+
stats[:task_total][task.id] = {:secs => 0} if !stats[:task_total].key?(task.id)
|
25
|
+
stats[:task_total][task.id][:secs] += wt[:worktime].to_i
|
26
|
+
|
27
|
+
stats[:days_total][date.date] = {:secs => 0, :tasks => {}} if !stats[:days_total].key?(date.date)
|
28
|
+
stats[:days_total][date.date][:secs] += wt[:worktime].to_i
|
29
|
+
stats[:days_total][date.date][:tasks][task.id] = task
|
30
|
+
end
|
31
|
+
|
32
|
+
table = Gtk::Table.new(4, 4)
|
33
|
+
row = 0
|
34
|
+
|
35
|
+
stats[:days_total].keys.sort.each do |day_no|
|
36
|
+
date = Knj::Datet.in(Time.new(date.year, date.month, day_no))
|
37
|
+
day_title = Gtk::Label.new
|
38
|
+
day_title.markup = "<b>#{date.out(:time => false)}</b>"
|
39
|
+
day_title.xalign = 0
|
40
|
+
|
41
|
+
day_sum_float = stats[:days_total][day_no][:secs].to_f / 3600.to_f
|
42
|
+
day_sum = Gtk::Label.new
|
43
|
+
day_sum.markup = "<b>#{Knj::Locales.number_out(day_sum_float, 2)}</b>"
|
44
|
+
day_sum.xalign = 1
|
45
|
+
|
46
|
+
table.attach(day_title, 0, 2, row, row + 1)
|
47
|
+
table.attach(day_sum, 3, 4, row, row + 1)
|
48
|
+
row += 1
|
49
|
+
|
50
|
+
stats[:days_total][day_no][:tasks].each do |task_id, task|
|
51
|
+
task_title = Gtk::Label.new(task.title)
|
52
|
+
task_title.xalign = 0
|
53
|
+
|
54
|
+
task_sum_float = stats[:task_total][task_id][:secs].to_f / 3600.to_f
|
55
|
+
task_sum = Gtk::Label.new(Knj::Locales.number_out(task_sum_float, 2))
|
56
|
+
task_sum.xalign = 1
|
57
|
+
|
58
|
+
table.attach(Gtk::Label.new(""), 0, 1, row, row + 1)
|
59
|
+
table.attach(task_title, 1, 2, row, row + 1)
|
60
|
+
table.attach(Gtk::Label.new("Company"), 2, 3, row, row + 1)
|
61
|
+
table.attach(task_sum, 3, 4, row, row + 1)
|
62
|
+
row += 1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
@gui["boxContent"].pack_start(table)
|
67
|
+
end
|
68
|
+
|
69
|
+
def on_btnNext_clicked
|
70
|
+
print "Next.\n"
|
71
|
+
end
|
72
|
+
|
73
|
+
def on_btnPrevious_clicked
|
74
|
+
print "Previous.\n"
|
75
|
+
end
|
76
|
+
end
|
data/lib/openall_time_applet.rb
CHANGED
@@ -1,27 +1,38 @@
|
|
1
1
|
require "rubygems"
|
2
|
-
|
2
|
+
|
3
|
+
if ENV["HOME"] == "/home/kaspernj"
|
4
|
+
#For development.
|
5
|
+
require "/home/kaspernj/Dev/Ruby/knjrbfw/lib/knjrbfw"
|
6
|
+
else
|
7
|
+
require "knjrbfw"
|
8
|
+
end
|
9
|
+
|
3
10
|
require "gtk2"
|
4
11
|
require "sqlite3"
|
5
12
|
require "gettext"
|
6
13
|
require "base64"
|
7
14
|
|
8
15
|
require "knj/gtk2"
|
16
|
+
require "knj/gtk2_cb"
|
9
17
|
require "knj/gtk2_tv"
|
10
18
|
require "knj/gtk2_statuswindow"
|
11
19
|
|
12
20
|
class Openall_time_applet
|
21
|
+
#Shortcut to start the application. Used by the Ubuntu-package.
|
13
22
|
def self.exec
|
14
23
|
require "#{File.dirname(__FILE__)}/../bin/openall_time_applet"
|
15
24
|
end
|
16
25
|
|
17
26
|
#Subclass controlling autoloading of models.
|
18
27
|
class Models
|
28
|
+
#Autoloader for subclasses.
|
19
29
|
def self.const_missing(name)
|
20
30
|
require "../models/#{name.to_s.downcase}.rb"
|
21
31
|
return Openall_time_applet::Models.const_get(name)
|
22
32
|
end
|
23
33
|
end
|
24
34
|
|
35
|
+
#Subclass holding all GUI-subclasses and autoloading of them.
|
25
36
|
class Gui
|
26
37
|
#Autoloader for subclasses.
|
27
38
|
def self.const_missing(name)
|
@@ -47,7 +58,7 @@ class Openall_time_applet
|
|
47
58
|
end
|
48
59
|
|
49
60
|
#Various readable variables.
|
50
|
-
attr_reader :db, :ob
|
61
|
+
attr_reader :db, :ob, :timelog_active, :timelog_active_time
|
51
62
|
|
52
63
|
#Config controlling paths and more.
|
53
64
|
CONFIG = {
|
@@ -63,7 +74,8 @@ class Openall_time_applet
|
|
63
74
|
@db = Knj::Db.new(
|
64
75
|
:type => "sqlite3",
|
65
76
|
:path => CONFIG[:db_path],
|
66
|
-
:return_keys => "symbols"
|
77
|
+
:return_keys => "symbols",
|
78
|
+
:index_append_table_name => true
|
67
79
|
)
|
68
80
|
|
69
81
|
#Models-handeler.
|
@@ -74,17 +86,28 @@ class Openall_time_applet
|
|
74
86
|
:class_pre => "",
|
75
87
|
:module => Openall_time_applet::Models
|
76
88
|
)
|
89
|
+
@ob.events.connect(:no_name) do |event, classname|
|
90
|
+
_("not set")
|
91
|
+
end
|
77
92
|
|
78
93
|
#Options used to save various information (Openall-username and such).
|
79
94
|
Knj::Opts.init("knjdb" => @db, "table" => "Option")
|
95
|
+
|
96
|
+
#Set crash-operation to save tracked time instead of loosing it.
|
97
|
+
Kernel.at_exit(&self.method(:destroy))
|
80
98
|
end
|
81
99
|
|
82
100
|
#Updates the database according to the db-schema.
|
83
101
|
def update_db
|
84
102
|
require "../conf/db_schema.rb"
|
85
|
-
|
103
|
+
Knj::Db::Revision.new.init_db("db" => @db, "schema" => Openall_time_applet::DB_SCHEMA)
|
86
104
|
end
|
87
105
|
|
106
|
+
#Creates a connection to OpenAll, logs in, yields the connection and destroys it again.
|
107
|
+
#===Examples
|
108
|
+
# oata.oa_conn do |conn|
|
109
|
+
# task_list = conn.task_list
|
110
|
+
# end
|
88
111
|
def oa_conn
|
89
112
|
begin
|
90
113
|
conn = Openall_time_applet::Connection.new(
|
@@ -122,6 +145,91 @@ class Openall_time_applet
|
|
122
145
|
def show_overview
|
123
146
|
Openall_time_applet::Gui::Win_overview.new(:oata => self)
|
124
147
|
end
|
148
|
+
|
149
|
+
def show_worktime_overview
|
150
|
+
Openall_time_applet::Gui::Win_worktime_overview.new(:oata => self)
|
151
|
+
end
|
152
|
+
|
153
|
+
#Updates the task-cache.
|
154
|
+
def update_task_cache
|
155
|
+
@ob.static(:Task, :update_cache, {:oata => self})
|
156
|
+
end
|
157
|
+
|
158
|
+
#Updates the worktime-cache.
|
159
|
+
def update_worktime_cache
|
160
|
+
@ob.static(:Worktime, :update_cache, {:oata => self})
|
161
|
+
end
|
162
|
+
|
163
|
+
#Pushes time-updates to OpenAll.
|
164
|
+
def push_time_updates
|
165
|
+
@ob.static(:Timelog, :push_time_updates, {:oata => self})
|
166
|
+
end
|
167
|
+
|
168
|
+
#Refreshes task-cache, create missing worktime from timelogs and push tracked time to timelogs. Shows a status-window while doing so.
|
169
|
+
def sync
|
170
|
+
sw = Knj::Gtk2::StatusWindow.new
|
171
|
+
|
172
|
+
if @timelog_active
|
173
|
+
timelog_active = @timelog_active
|
174
|
+
self.timelog_stop_tracking
|
175
|
+
end
|
176
|
+
|
177
|
+
Knj::Thread.new do
|
178
|
+
begin
|
179
|
+
sw.label = _("Updating task-cache.")
|
180
|
+
self.update_task_cache
|
181
|
+
sw.percent = 0.3
|
182
|
+
|
183
|
+
sw.label = _("Updating worktime-cache.")
|
184
|
+
self.update_worktime_cache
|
185
|
+
sw.percent = 0.66
|
186
|
+
|
187
|
+
sw.label = _("Pushing time-updates.")
|
188
|
+
self.push_time_updates
|
189
|
+
sw.percent = 1
|
190
|
+
|
191
|
+
sw.label = _("Done")
|
192
|
+
|
193
|
+
sleep 1
|
194
|
+
rescue => e
|
195
|
+
Knj::Gtk2.msgbox("msg" => Knj::Errors.error_str(e), "type" => "warning", "title" => _("Error"), "run" => false)
|
196
|
+
ensure
|
197
|
+
sw.destroy
|
198
|
+
self.timelog_active = timelog_active if timelog_active
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
#Stops tracking a timelog. Saves time tracked and sets sync-flag.
|
204
|
+
def timelog_stop_tracking
|
205
|
+
if @timelog_active
|
206
|
+
secs_passed = Time.now.to_i - @timelog_active_time.to_i
|
207
|
+
@timelog_active.update(
|
208
|
+
:time => @timelog_active[:time].to_i + secs_passed,
|
209
|
+
:sync_need => 1
|
210
|
+
)
|
211
|
+
end
|
212
|
+
|
213
|
+
@timelog_active = nil
|
214
|
+
@timelog_active_time = nil
|
215
|
+
end
|
216
|
+
|
217
|
+
#Sets a new timelog to track. Stops tracking of previous timelog if already tracking.
|
218
|
+
def timelog_active=(timelog)
|
219
|
+
self.timelog_stop_tracking
|
220
|
+
|
221
|
+
@timelog_active = timelog
|
222
|
+
@timelog_active_time = Time.new
|
223
|
+
end
|
224
|
+
|
225
|
+
#Saves tracking-status if tracking. Stops Gtks main loop.
|
226
|
+
def destroy
|
227
|
+
self.timelog_stop_tracking
|
228
|
+
|
229
|
+
#Use quit-variable to avoid Gtk-warnings.
|
230
|
+
Gtk.main_quit if @quit != true
|
231
|
+
@quit = true
|
232
|
+
end
|
125
233
|
end
|
126
234
|
|
127
235
|
#Gettext support.
|
data/models/task.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
class Openall_time_applet::Models::Task < Knj::Datarow
|
2
|
+
has_many [
|
3
|
+
[:Timelog, :task_id, :timelogs]
|
4
|
+
]
|
5
|
+
|
6
|
+
def self.update_cache(d, args)
|
7
|
+
res = nil
|
8
|
+
args[:oata].oa_conn do |conn|
|
9
|
+
res = conn.request(:getAllTasksForUser)
|
10
|
+
end
|
11
|
+
|
12
|
+
res.each do |task_data|
|
13
|
+
task = self.ob.get_by(:Task, {"openall_uid" => task_data["uid"]})
|
14
|
+
task_data = {
|
15
|
+
:openall_uid => task_data["uid"],
|
16
|
+
:title => task_data["title"]
|
17
|
+
}
|
18
|
+
|
19
|
+
if task
|
20
|
+
task.update(task_data)
|
21
|
+
else
|
22
|
+
task = self.ob.add(:Task, task_data)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/models/timelog.rb
CHANGED
@@ -1,3 +1,68 @@
|
|
1
1
|
class Openall_time_applet::Models::Timelog < Knj::Datarow
|
2
|
+
has_one [
|
3
|
+
:Task
|
4
|
+
]
|
2
5
|
|
6
|
+
#Treat data before inserting into database.
|
7
|
+
def self.add(d)
|
8
|
+
d.data[:time] = 0 if d.data[:time].to_s.strip.length <= 0
|
9
|
+
d.data[:time_transport] = 0 if d.data[:time_transport].to_s.strip.length <= 0
|
10
|
+
end
|
11
|
+
|
12
|
+
#Pushes timelogs and time to OpenAll.
|
13
|
+
def self.push_time_updates(d, args)
|
14
|
+
args[:oata].oa_conn do |conn|
|
15
|
+
#Go through timelogs that needs syncing and has a task set.
|
16
|
+
self.ob.list(:Timelog, {"sync_need" => 1, "task_id_not" => 0}) do |timelog|
|
17
|
+
secs_sum = timelog[:time].to_i + timelog[:time_transport].to_i
|
18
|
+
next if secs_sum <= 0
|
19
|
+
|
20
|
+
#The timelog has not yet been created in OpenAll - create it!
|
21
|
+
if timelog[:openall_uid].to_i == 0
|
22
|
+
res = conn.request(
|
23
|
+
:method => :createWorktime,
|
24
|
+
:post => {
|
25
|
+
:task_uid => timelog.task[:openall_uid].to_i,
|
26
|
+
:comment => timelog[:descr]
|
27
|
+
}
|
28
|
+
)
|
29
|
+
timelog[:openall_uid] = res["worktime_uid"]
|
30
|
+
end
|
31
|
+
|
32
|
+
#Push latest work-time.
|
33
|
+
res = conn.request(
|
34
|
+
:method => :pushTimeToWorktime,
|
35
|
+
:post => {
|
36
|
+
:worktime_uid => timelog[:openall_uid].to_i,
|
37
|
+
:secs => timelog[:time].to_i,
|
38
|
+
:secs_transport => timelog[:time_transport].to_i
|
39
|
+
}
|
40
|
+
)
|
41
|
+
timelog.update(
|
42
|
+
:time => 0,
|
43
|
+
:time_transport => 0,
|
44
|
+
:sync_need => 0,
|
45
|
+
:sync_last => Time.now
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
#Returns a short one-line short description.
|
52
|
+
def descr_short
|
53
|
+
descr = self[:descr].to_s.gsub("\n", " ").gsub(/\s{2,}/, " ")
|
54
|
+
descr = Knj::Strings.shorten(descr, 20)
|
55
|
+
descr = "[#{_("no description")}]" if descr.to_s.strip.length <= 0
|
56
|
+
return descr
|
57
|
+
end
|
58
|
+
|
59
|
+
#Returns the time as a human readable format.
|
60
|
+
def time_as_human
|
61
|
+
return Knj::Strings.secs_to_human_time_str(self[:time])
|
62
|
+
end
|
63
|
+
|
64
|
+
#Returns the transport-time as a human readable format.
|
65
|
+
def time_transport_as_human
|
66
|
+
return Knj::Strings.secs_to_human_time_str(self[:time_transport])
|
67
|
+
end
|
3
68
|
end
|
data/models/worktime.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
class Openall_time_applet::Models::Worktime < Knj::Datarow
|
2
|
+
has_one [
|
3
|
+
:Task
|
4
|
+
]
|
5
|
+
|
6
|
+
def self.update_cache(d, args)
|
7
|
+
res = nil
|
8
|
+
args[:oata].oa_conn do |conn|
|
9
|
+
res = conn.request(:getLatestWorktimes)
|
10
|
+
end
|
11
|
+
|
12
|
+
#Update all worktimes.
|
13
|
+
found = []
|
14
|
+
res.each do |wt_d|
|
15
|
+
found << wt_d["uid"]
|
16
|
+
task = self.ob.get_by(:Task, {"openall_uid" => wt_d["task_uid"]})
|
17
|
+
|
18
|
+
save_hash = {
|
19
|
+
:openall_uid => wt_d["uid"],
|
20
|
+
:task_id => task.id,
|
21
|
+
:timestamp => Knj::Datet.in(wt_d["timestamp"]),
|
22
|
+
:worktime => Knj::Strings.human_time_str_to_secs(wt_d["worktime"]),
|
23
|
+
:transporttime => Knj::Strings.human_time_str_to_secs(wt_d["transporttime"]),
|
24
|
+
:comment => wt_d["comment"]
|
25
|
+
}
|
26
|
+
|
27
|
+
wt = self.ob.get_by(:Worktime, {"openall_uid" => wt_d["uid"]})
|
28
|
+
if wt
|
29
|
+
wt.update(save_hash)
|
30
|
+
else
|
31
|
+
wt = self.ob.add(:Worktime, save_hash)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
#Delete the ones not given.
|
36
|
+
list = self.ob.list(:Worktime, {"openall_uid_not" => found})
|
37
|
+
self.ob.deletes(list)
|
38
|
+
end
|
39
|
+
end
|
data/openall_time_applet.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{openall_time_applet}
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.2"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Kasper Johansen"]
|
12
|
-
s.date = %q{2012-05-
|
12
|
+
s.date = %q{2012-05-21}
|
13
13
|
s.default_executable = %q{openall_time_applet.rb}
|
14
14
|
s.description = %q{Off-line time-tracking for OpenAll with syncing when online.}
|
15
15
|
s.email = %q{k@spernj.org}
|
@@ -34,14 +34,18 @@ Gem::Specification.new do |s|
|
|
34
34
|
"glade/win_overview.glade",
|
35
35
|
"glade/win_preferences.glade",
|
36
36
|
"glade/win_timelog_edit.glade",
|
37
|
+
"glade/win_worktime_overview.glade",
|
37
38
|
"gui/trayicon.rb",
|
38
39
|
"gui/win_overview.rb",
|
39
40
|
"gui/win_preferences.rb",
|
40
41
|
"gui/win_timelog_edit.rb",
|
42
|
+
"gui/win_worktime_overview.rb",
|
41
43
|
"lib/openall_time_applet.rb",
|
42
44
|
"locales/da_DK/LC_MESSAGES/default.mo",
|
43
45
|
"locales/da_DK/LC_MESSAGES/default.po",
|
46
|
+
"models/task.rb",
|
44
47
|
"models/timelog.rb",
|
48
|
+
"models/worktime.rb",
|
45
49
|
"openall_time_applet.gemspec",
|
46
50
|
"spec/openall_time_applet_spec.rb",
|
47
51
|
"spec/spec_helper.rb"
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: openall_time_applet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.0.
|
5
|
+
version: 0.0.2
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Kasper Johansen
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2012-05-
|
13
|
+
date: 2012-05-21 00:00:00 +02:00
|
14
14
|
default_executable: openall_time_applet.rb
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -148,14 +148,18 @@ files:
|
|
148
148
|
- glade/win_overview.glade
|
149
149
|
- glade/win_preferences.glade
|
150
150
|
- glade/win_timelog_edit.glade
|
151
|
+
- glade/win_worktime_overview.glade
|
151
152
|
- gui/trayicon.rb
|
152
153
|
- gui/win_overview.rb
|
153
154
|
- gui/win_preferences.rb
|
154
155
|
- gui/win_timelog_edit.rb
|
156
|
+
- gui/win_worktime_overview.rb
|
155
157
|
- lib/openall_time_applet.rb
|
156
158
|
- locales/da_DK/LC_MESSAGES/default.mo
|
157
159
|
- locales/da_DK/LC_MESSAGES/default.po
|
160
|
+
- models/task.rb
|
158
161
|
- models/timelog.rb
|
162
|
+
- models/worktime.rb
|
159
163
|
- openall_time_applet.gemspec
|
160
164
|
- spec/openall_time_applet_spec.rb
|
161
165
|
- spec/spec_helper.rb
|
@@ -173,7 +177,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
173
177
|
requirements:
|
174
178
|
- - ">="
|
175
179
|
- !ruby/object:Gem::Version
|
176
|
-
hash:
|
180
|
+
hash: 55925093073077767
|
177
181
|
segments:
|
178
182
|
- 0
|
179
183
|
version: "0"
|