knjrbfw 0.0.21 → 0.0.22
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/knjrbfw.gemspec +2 -2
- data/lib/knj/arrayext.rb +17 -3
- data/lib/knj/cmd_parser.rb +26 -4
- data/lib/knj/csv.rb +3 -0
- data/lib/knj/datarow.rb +19 -4
- data/lib/knj/datet.rb +1 -1
- data/lib/knj/gettext_threadded.rb +2 -2
- data/lib/knj/google_sitemap.rb +57 -18
- data/lib/knj/http2.rb +100 -56
- data/lib/knj/knjdb/drivers/mysql/knjdb_mysql.rb +10 -5
- data/lib/knj/knjdb/drivers/mysql/knjdb_mysql_columns.rb +1 -1
- data/lib/knj/knjdb/drivers/mysql/knjdb_mysql_indexes.rb +11 -1
- data/lib/knj/knjdb/drivers/mysql/knjdb_mysql_tables.rb +0 -2
- data/lib/knj/knjdb/libknjdb.rb +41 -1
- data/lib/knj/knjdb/revision.rb +9 -2
- data/lib/knj/locales.rb +16 -9
- data/lib/knj/objects.rb +83 -61
- data/lib/knj/objects/objects_sqlhelper.rb +1 -1
- data/lib/knj/os.rb +5 -1
- data/lib/knj/php.rb +2 -0
- data/lib/knj/process_meta.rb +149 -14
- data/lib/knj/scripts/process_meta_exec.rb +36 -2
- data/lib/knj/unix_proc.rb +42 -33
- data/lib/knj/web.rb +34 -12
- data/lib/knj/wref.rb +4 -2
- metadata +15 -15
data/lib/knj/os.rb
CHANGED
@@ -194,9 +194,13 @@ module Knj::Os
|
|
194
194
|
if self.os == "linux"
|
195
195
|
unix_proc = Knj::Unix_proc.find_self
|
196
196
|
if unix_proc
|
197
|
-
if match_cmd = unix_proc["cmd"].match(/^(\/usr\/bin\/|)((j|iron|)ruby([\d\.-]
|
197
|
+
if match_cmd = unix_proc["cmd"].match(/^(\/usr\/bin\/|)((j|iron|)ruby([\d\.-]*))(\s+|$)/)
|
198
198
|
return "#{match_cmd[1]}#{match_cmd[2]}"
|
199
|
+
else
|
200
|
+
raise "Could not match the executed command from the process."
|
199
201
|
end
|
202
|
+
else
|
203
|
+
raise "Could not find the self-process."
|
200
204
|
end
|
201
205
|
end
|
202
206
|
|
data/lib/knj/php.rb
CHANGED
data/lib/knj/process_meta.rb
CHANGED
@@ -7,7 +7,10 @@ class Knj::Process_meta
|
|
7
7
|
def initialize(args = {})
|
8
8
|
@args = args
|
9
9
|
@objects = {}
|
10
|
+
|
11
|
+
#These variables are used to free memory in the subprocess, by using ObjectSpace#define_finalizer. The Mutex is the avoid problems when writing to the finalize-array multithreadded.
|
10
12
|
@finalize = []
|
13
|
+
@finalize_mutex = Mutex.new
|
11
14
|
|
12
15
|
if @args["exec_path"]
|
13
16
|
exec_path = @args["exec_path"]
|
@@ -69,22 +72,32 @@ class Knj::Process_meta
|
|
69
72
|
end
|
70
73
|
|
71
74
|
def proxy_finalizer(id)
|
72
|
-
@
|
75
|
+
@finalize_mutex.synchronize do
|
76
|
+
@finalize << id
|
77
|
+
end
|
73
78
|
end
|
74
79
|
|
75
80
|
def check_finalizers
|
76
81
|
return nil if @finalize.empty?
|
77
82
|
|
78
|
-
|
79
|
-
@
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
83
|
+
finalize = nil
|
84
|
+
@finalize_mutex.synchronize do
|
85
|
+
finalize = @finalize
|
86
|
+
@finalize = []
|
87
|
+
|
88
|
+
begin
|
89
|
+
@process.send("obj" => {
|
90
|
+
"type" => "unset_multiple",
|
91
|
+
"var_names" => finalize
|
92
|
+
})
|
93
|
+
rescue => e
|
94
|
+
if e.message.to_s.index("Var-name didnt exist when trying to unset:")
|
95
|
+
#ignore.
|
96
|
+
else
|
97
|
+
raise e
|
98
|
+
end
|
99
|
+
end
|
85
100
|
end
|
86
|
-
|
87
|
-
@finalize -= remove
|
88
101
|
end
|
89
102
|
|
90
103
|
#Parses the arguments given. Proxy-object-arguments will be their natural objects in the subprocess.
|
@@ -184,6 +197,10 @@ class Knj::Process_meta
|
|
184
197
|
|
185
198
|
#Spawns a new object in the subprocess by that classname, with those arguments and with that block.
|
186
199
|
def new(class_name, *args, &block)
|
200
|
+
#We need to check finalizers first, so we wont accidently reuse an ID, which will then be unset in the process.
|
201
|
+
self.check_finalizers
|
202
|
+
|
203
|
+
#Spawn and return the object.
|
187
204
|
return self.spawn_object(class_name, nil, *args, &block)
|
188
205
|
end
|
189
206
|
|
@@ -224,19 +241,23 @@ class Knj::Process_meta
|
|
224
241
|
|
225
242
|
#Calls a method on an object and returns the result.
|
226
243
|
def call_object(args, &block)
|
227
|
-
self.check_finalizers
|
228
|
-
|
229
244
|
if args.key?("capture_return")
|
230
245
|
capture_return = args["capture_return"]
|
231
246
|
else
|
232
247
|
capture_return = true
|
233
248
|
end
|
234
249
|
|
250
|
+
if args["buffered"]
|
251
|
+
type = "call_object_buffered"
|
252
|
+
else
|
253
|
+
type = "call_object_block"
|
254
|
+
end
|
255
|
+
|
235
256
|
res = @process.send(
|
236
257
|
{
|
237
258
|
"buffer_use" => args["buffer_use"],
|
238
259
|
"obj" => {
|
239
|
-
"type" =>
|
260
|
+
"type" => type,
|
240
261
|
"var_name" => args["var_name"],
|
241
262
|
"method_name" => args["method_name"],
|
242
263
|
"capture_return" => capture_return,
|
@@ -329,7 +350,12 @@ class Knj::Process_meta
|
|
329
350
|
@process.send("obj" => {"type" => "exit"})
|
330
351
|
@err_thread.kill if @err_thread
|
331
352
|
@process.destroy
|
332
|
-
|
353
|
+
|
354
|
+
begin
|
355
|
+
Process.kill("TERM", @pid)
|
356
|
+
rescue Errno::ESRCH
|
357
|
+
#Process is already dead - ignore.
|
358
|
+
end
|
333
359
|
|
334
360
|
begin
|
335
361
|
sleep 0.1
|
@@ -410,4 +436,113 @@ class Knj::Process_meta::Proxy_obj
|
|
410
436
|
&block
|
411
437
|
)
|
412
438
|
end
|
439
|
+
|
440
|
+
def _pm_buffered_caller(args)
|
441
|
+
return Knj::Process_meta::Proxy_obj::Buffered_caller.new({
|
442
|
+
:name => @args[:name],
|
443
|
+
:process_meta => @args[:process_meta]
|
444
|
+
}.merge(args))
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
class Knj::Process_meta::Proxy_obj::Buffered_caller
|
449
|
+
def initialize(args)
|
450
|
+
@args = args
|
451
|
+
@buffer = []
|
452
|
+
@mutex = Mutex.new
|
453
|
+
@mutex_write = Mutex.new
|
454
|
+
@count = 0
|
455
|
+
@debug = @args[:debug] if @args[:debug]
|
456
|
+
|
457
|
+
if @args[:count_to]
|
458
|
+
@count_to = @args[:count_to]
|
459
|
+
else
|
460
|
+
@count_to = 1000
|
461
|
+
end
|
462
|
+
|
463
|
+
@buffer_max = @count_to * 2
|
464
|
+
@threads = [] if @args[:async]
|
465
|
+
end
|
466
|
+
|
467
|
+
def method_missing(method_name, *args)
|
468
|
+
if method_name.to_s == @args[:method_name].to_s
|
469
|
+
self._pm_call(*args)
|
470
|
+
else
|
471
|
+
raise NoMethodError, "No such method: '#{method_name}'."
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
def _pm_call(*args)
|
476
|
+
raise @raise_error if @raise_error
|
477
|
+
|
478
|
+
@mutex.synchronize do
|
479
|
+
while @count >= @count_to and @buffer.length >= @buffer_max
|
480
|
+
STDOUT.print "Waiting for write to complete...\n" if @debug
|
481
|
+
sleep 0.1
|
482
|
+
end
|
483
|
+
|
484
|
+
STDOUT.print "Adding to buffer #{@buffer.length}...\n" if @debug
|
485
|
+
@buffer << args
|
486
|
+
@count += 1
|
487
|
+
end
|
488
|
+
|
489
|
+
self._pm_flush if @count >= @count_to and !@writing
|
490
|
+
return nil
|
491
|
+
end
|
492
|
+
|
493
|
+
def _pm_flush(*args)
|
494
|
+
raise @raise_error if @raise_error
|
495
|
+
|
496
|
+
buffer = nil
|
497
|
+
@mutex.synchronize do
|
498
|
+
buffer = @buffer
|
499
|
+
@buffer = []
|
500
|
+
@count = 0
|
501
|
+
end
|
502
|
+
|
503
|
+
if @args[:async]
|
504
|
+
begin
|
505
|
+
@threads << Thread.new do
|
506
|
+
self._pm_flush_real(buffer)
|
507
|
+
end
|
508
|
+
rescue => e
|
509
|
+
@raise_error = e
|
510
|
+
end
|
511
|
+
|
512
|
+
return nil
|
513
|
+
else
|
514
|
+
return self._pm_flush_real(buffer)
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
def _pm_flush_real(buffer)
|
519
|
+
@mutex_write.synchronize do
|
520
|
+
STDOUT.print "Writing...\n" if @debug
|
521
|
+
@writing = true
|
522
|
+
|
523
|
+
begin
|
524
|
+
return @args[:process_meta].call_object(
|
525
|
+
"var_name" => @args[:name],
|
526
|
+
"method_name" => @args[:method_name],
|
527
|
+
"args" => buffer,
|
528
|
+
"buffered" => true,
|
529
|
+
"capture_return" => false
|
530
|
+
)
|
531
|
+
ensure
|
532
|
+
@writing = false
|
533
|
+
end
|
534
|
+
end
|
535
|
+
end
|
536
|
+
|
537
|
+
def _pm_close
|
538
|
+
self._pm_flush
|
539
|
+
|
540
|
+
if @args[:async]
|
541
|
+
@threads.each do |thread|
|
542
|
+
thread.join
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
raise @raise_error if @raise_error
|
547
|
+
end
|
413
548
|
end
|
@@ -22,6 +22,8 @@ objects = {}
|
|
22
22
|
|
23
23
|
if obj.is_a?(Hash)
|
24
24
|
if obj["type"] == "spawn_object"
|
25
|
+
raise "An object by that name already exists: '#{obj["var_name"]}'." if objects.key?(obj["var_name"])
|
26
|
+
|
25
27
|
#Fix new integer.
|
26
28
|
if obj["class_name"].to_s == "Integer" or obj["class_name"].to_s == "Fixnum"
|
27
29
|
objects[obj["var_name"]] = obj["args"].first.to_i
|
@@ -35,7 +37,7 @@ objects = {}
|
|
35
37
|
d.answer("type" => "success")
|
36
38
|
elsif obj["type"] == "proxy_from_call"
|
37
39
|
raise "No 'var_name' was given in arguments." if !obj["var_name"]
|
38
|
-
raise "No object by that name: '#{obj["proxy_obj"]}' in '#{objects}'." if !objects.key?(obj["proxy_obj"])
|
40
|
+
raise "No object by that name when trying to do 'proxy_from_call': '#{obj["proxy_obj"]}' in '#{objects}'." if !objects.key?(obj["proxy_obj"])
|
39
41
|
obj_to_call = objects[obj["proxy_obj"]]
|
40
42
|
res = obj_to_call.__send__(obj["method_name"], *obj["args"])
|
41
43
|
objects[obj["var_name"]] = res
|
@@ -57,6 +59,28 @@ objects = {}
|
|
57
59
|
raise "No object by that name: '#{obj["var_name"]}'." if !obj
|
58
60
|
res = obj_to_call.__send__(obj["method_name"], *obj["args"])
|
59
61
|
res = nil if obj["capture_return"] == false
|
62
|
+
d.answer("type" => "call_object_success", "result" => res)
|
63
|
+
elsif obj["type"] == "call_object_buffered"
|
64
|
+
raise "Invalid var-name: '#{obj["var_name"]}'." if obj["var_name"].to_s.strip.length <= 0
|
65
|
+
|
66
|
+
obj_to_call = objects[obj["var_name"]]
|
67
|
+
raise "No object by that name: '#{obj["var_name"]}'." if !obj
|
68
|
+
capt_return = obj["capture_return"]
|
69
|
+
|
70
|
+
if capt_return != false
|
71
|
+
ret = []
|
72
|
+
else
|
73
|
+
ret = nil
|
74
|
+
end
|
75
|
+
|
76
|
+
obj["args"].each do |args|
|
77
|
+
if capt_return != false
|
78
|
+
ret << obj_to_call.__send__(obj["method_name"], *args)
|
79
|
+
else
|
80
|
+
obj_to_call.__send__(obj["method_name"], *args)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
60
84
|
d.answer("type" => "call_object_success", "result" => res)
|
61
85
|
elsif obj["type"] == "call_object_block"
|
62
86
|
raise "Invalid var-name: '#{obj["var_name"]}'." if obj["var_name"].to_s.strip.length <= 0
|
@@ -77,9 +101,19 @@ objects = {}
|
|
77
101
|
end
|
78
102
|
elsif obj["type"] == "unset"
|
79
103
|
raise "Invalid var-name: '#{obj["var_name"]}'." if obj["var_name"].to_s.strip.length <= 0
|
80
|
-
raise "Var-name
|
104
|
+
raise Knj::Errors::NotFound, "Var-name didnt exist when trying to unset: '#{obj["var_name"]}'." if !objects.key?(obj["var_name"])
|
81
105
|
objects.delete(obj["var_name"])
|
82
106
|
d.answer("type" => "unset_success")
|
107
|
+
elsif obj["type"] == "unset_multiple"
|
108
|
+
err = nil
|
109
|
+
obj["var_names"].each do |var_name|
|
110
|
+
err = [Knj::Errors::InvalidData, "Invalid var-name: '#{var_name}'."] if var_name.to_s.strip.length <= 0
|
111
|
+
err = [Knj::Errors::NotFound, "Var-name didnt exist when trying to unset: '#{var_name}'."] if !objects.key?(var_name)
|
112
|
+
objects.delete(var_name)
|
113
|
+
end
|
114
|
+
|
115
|
+
raise err[0], err[1] if err
|
116
|
+
d.answer("type" => "unset_success")
|
83
117
|
elsif obj["type"] == "static"
|
84
118
|
const = Knj::Strings.const_get_full(obj["const"])
|
85
119
|
res = const.__send__(obj["method_name"], *obj["args"], &block)
|
data/lib/knj/unix_proc.rb
CHANGED
@@ -1,17 +1,23 @@
|
|
1
|
+
require "#{$knjpath}wref"
|
2
|
+
|
1
3
|
class Knj::Unix_proc
|
2
4
|
attr_reader :data
|
3
|
-
|
5
|
+
|
6
|
+
PROCS = Knj::Wref_map.new
|
7
|
+
MUTEX = Mutex.new
|
4
8
|
|
5
9
|
def self.spawn(data)
|
6
|
-
|
10
|
+
pid = data["pid"].to_i
|
7
11
|
|
8
|
-
|
12
|
+
begin
|
13
|
+
proc_ele = PROCS[pid]
|
9
14
|
proc_ele.update_data(data)
|
10
|
-
|
11
|
-
|
15
|
+
rescue WeakRef::RefError
|
16
|
+
proc_ele = Knj::Unix_proc.new(data)
|
17
|
+
PROCS[pid] = proc_ele
|
12
18
|
end
|
13
19
|
|
14
|
-
return
|
20
|
+
return proc_ele
|
15
21
|
end
|
16
22
|
|
17
23
|
def self.list(args = {})
|
@@ -23,40 +29,43 @@ class Knj::Unix_proc
|
|
23
29
|
cmdstr << " | #{grepstr}"
|
24
30
|
end
|
25
31
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
res.scan(/^(\S+)\s+([0-9]+)\s+([0-9.]+)\s+([0-9.]+)\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+ (.+)($|\n)/) do |match|
|
30
|
-
pid = match[1]
|
31
|
-
|
32
|
-
data = {
|
33
|
-
"user" => match[0],
|
34
|
-
"pid" => pid,
|
35
|
-
"cpu_last" => match[2],
|
36
|
-
"ram_last" => match[3],
|
37
|
-
"cmd" => match[4],
|
38
|
-
"app" => File.basename(match[4])
|
39
|
-
}
|
40
|
-
|
41
|
-
next if (!args.key?("ignore_self") or args["ignore_self"]) and match[1].to_i == $$.to_i
|
42
|
-
next if grepstr.length > 0 and match[4].index(grepstr) != nil #dont return current process.
|
32
|
+
MUTEX.synchronize do
|
33
|
+
ret = []
|
34
|
+
res = Knj::Os.shellcmd(cmdstr)
|
43
35
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
36
|
+
res.scan(/^(\S+)\s+([0-9]+)\s+([0-9.]+)\s+([0-9.]+)\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+ (.+)($|\n)/) do |match|
|
37
|
+
pid = match[1]
|
38
|
+
|
39
|
+
data = {
|
40
|
+
"user" => match[0],
|
41
|
+
"pid" => pid,
|
42
|
+
"cpu_last" => match[2],
|
43
|
+
"ram_last" => match[3],
|
44
|
+
"cmd" => match[4],
|
45
|
+
"app" => File.basename(match[4])
|
46
|
+
}
|
47
|
+
|
48
|
+
next if (!args.key?("ignore_self") or args["ignore_self"]) and match[1].to_i == $$.to_i
|
49
|
+
next if grepstr.length > 0 and match[4].index(grepstr) != nil #dont return current process.
|
50
|
+
|
51
|
+
if args.key?("pids")
|
52
|
+
found = false
|
53
|
+
args["pids"].each do |pid_given|
|
54
|
+
if pid_given.to_s == pid.to_s
|
55
|
+
found = true
|
56
|
+
break
|
57
|
+
end
|
50
58
|
end
|
59
|
+
|
60
|
+
next if !found
|
51
61
|
end
|
52
62
|
|
53
|
-
|
63
|
+
ret << Knj::Unix_proc.spawn(data)
|
54
64
|
end
|
55
65
|
|
56
|
-
|
66
|
+
PROCS.clean
|
67
|
+
return ret
|
57
68
|
end
|
58
|
-
|
59
|
-
return ret
|
60
69
|
end
|
61
70
|
|
62
71
|
def self.find_self
|
data/lib/knj/web.rb
CHANGED
@@ -466,7 +466,7 @@ class Knj::Web
|
|
466
466
|
Knj::ArrayExt.hash_sym(args)
|
467
467
|
|
468
468
|
if args.key?(:value)
|
469
|
-
if args[:value].is_a?(Array) and args[:value].first.is_a?(NilClass)
|
469
|
+
if args[:value].is_a?(Array) and (args[:value].first.is_a?(NilClass) or args[:value].first == false)
|
470
470
|
value = nil
|
471
471
|
elsif args[:value].is_a?(Array)
|
472
472
|
if !args[:value][2] or args[:value][2] == :key
|
@@ -530,6 +530,7 @@ class Knj::Web
|
|
530
530
|
}
|
531
531
|
attr.merge!(args[:attr]) if args[:attr]
|
532
532
|
attr["disabled"] = "disabled" if args[:disabled]
|
533
|
+
attr["maxlength"] = args[:maxlength] if args.key?(:maxlength)
|
533
534
|
|
534
535
|
raise "No name given to the Web::input()-method." if !args[:name] and args[:type] != :info and args[:type] != :textshow and args[:type] != :plain and args[:type] != :spacer and args[:type] != :headline
|
535
536
|
|
@@ -553,6 +554,12 @@ class Knj::Web
|
|
553
554
|
classes_tr_html = ""
|
554
555
|
end
|
555
556
|
|
557
|
+
if args.key?(:title)
|
558
|
+
title_html = args[:title].to_s.html
|
559
|
+
elsif args.key?(:title_html)
|
560
|
+
title_html = args[:title_html]
|
561
|
+
end
|
562
|
+
|
556
563
|
html = ""
|
557
564
|
|
558
565
|
classes = ["input_#{args[:type]}"]
|
@@ -567,17 +574,17 @@ class Knj::Web
|
|
567
574
|
html << "<tr#{classes_tr_html}>"
|
568
575
|
html << "<td colspan=\"2\" class=\"tdcheck\">"
|
569
576
|
html << "<input#{self.attr_html(attr)} />"
|
570
|
-
html << "<label for=\"#{args[:id].html}\">#{
|
577
|
+
html << "<label for=\"#{args[:id].html}\">#{title_html}</label>"
|
571
578
|
html << "</td>"
|
572
579
|
html << "</tr>"
|
573
580
|
elsif args[:type] == :headline
|
574
|
-
html << "<tr#{classes_tr_html}><td colspan=\"2\"><h2 class=\"input_headline\">#{
|
581
|
+
html << "<tr#{classes_tr_html}><td colspan=\"2\"><h2 class=\"input_headline\">#{title_html}</h2></td></tr>"
|
575
582
|
elsif args[:type] == :spacer
|
576
583
|
html << "<tr#{classes_tr_html}><td colspan=\"2\"> </td></tr>"
|
577
584
|
else
|
578
585
|
html << "<tr#{classes_tr_html}>"
|
579
586
|
html << "<td class=\"tdt\">"
|
580
|
-
html <<
|
587
|
+
html << title_html
|
581
588
|
html << "</td>"
|
582
589
|
html << "<td#{self.style_html(css)} class=\"tdc\">"
|
583
590
|
|
@@ -671,12 +678,7 @@ class Knj::Web
|
|
671
678
|
|
672
679
|
def self.opts(opthash, curvalue = nil, opts_args = {})
|
673
680
|
opts_args = {} if !opts_args
|
674
|
-
opts_args
|
675
|
-
if !key.is_a?(Symbol)
|
676
|
-
opts_args[key.to_sym] = value
|
677
|
-
opts_args.delete(key)
|
678
|
-
end
|
679
|
-
end
|
681
|
+
Knj::ArrayExt.hash_sym(opts_args)
|
680
682
|
|
681
683
|
return "" if !opthash
|
682
684
|
cname = curvalue.class.name
|
@@ -739,7 +741,7 @@ class Knj::Web
|
|
739
741
|
return "gecko"
|
740
742
|
elsif agent.index("msie") != nil
|
741
743
|
return "msie"
|
742
|
-
elsif agent.index("w3c") != nil or agent.index("baiduspider") != nil or agent.index("googlebot") != nil
|
744
|
+
elsif agent.index("w3c") != nil or agent.index("baiduspider") != nil or agent.index("googlebot") != nil or agent.index("bot") != nil
|
743
745
|
return "bot"
|
744
746
|
else
|
745
747
|
#print "Unknown agent: #{agent}"
|
@@ -887,16 +889,36 @@ class Knj::Web
|
|
887
889
|
browser = "bot"
|
888
890
|
title = "AhrefsBot"
|
889
891
|
version = match[1]
|
892
|
+
elsif agent.index("sosospider") != nil
|
893
|
+
browser = "bot"
|
894
|
+
title = "Bot"
|
895
|
+
version = "Sosospider"
|
890
896
|
else
|
891
897
|
browser = "unknown"
|
892
898
|
title = "(unknown browser)"
|
893
899
|
version = "(unknown version)"
|
894
900
|
end
|
895
901
|
|
902
|
+
os = nil
|
903
|
+
os_version = nil
|
904
|
+
if agent.index("linux") != nil
|
905
|
+
os = "linux"
|
906
|
+
elsif match = agent.match(/mac\s+os\s+x\s+([\d_+])/)
|
907
|
+
os = "mac"
|
908
|
+
elsif match = agent.match(/windows\s+nt\s+([\d\.]+)/)
|
909
|
+
os = "windows"
|
910
|
+
|
911
|
+
if match[1] == "5.1"
|
912
|
+
os_version = "xp"
|
913
|
+
end
|
914
|
+
end
|
915
|
+
|
896
916
|
return {
|
897
917
|
"browser" => browser,
|
898
918
|
"title" => title,
|
899
|
-
"version" => version
|
919
|
+
"version" => version,
|
920
|
+
"os" => os,
|
921
|
+
"os_version" => os_version
|
900
922
|
}
|
901
923
|
end
|
902
924
|
|