openall_time_applet 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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"
|