multistockphoto 0.8.0 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +9 -0
- data/Manifest.txt +1 -0
- data/bin/multistockphoto +162 -75
- data/bin/multistockphoto-gui +36 -1
- data/config.yaml +7 -0
- data/lib/multistockphoto/generic_site.rb +13 -1
- data/lib/multistockphoto/photo.rb +64 -33
- data/lib/multistockphoto/sender.rb +4 -0
- data/lib/multistockphoto/site_fotolia.rb +22 -1
- data/lib/multistockphoto/site_panthermedia.rb +132 -0
- data/lib/multistockphoto/site_photocase.rb +72 -59
- data/lib/multistockphoto/site_zoonar.rb +3 -2
- data/lib/multistockphoto/version.rb +1 -1
- data/lib/multistockphoto.rb +1 -0
- data/test/test_multistockphoto.rb +153 -49
- data/website/index.html +650 -900
- data/website/index.txt +236 -22
- metadata +15 -5
data/History.txt
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
== 0.8.1 2008-12-30
|
2
|
+
* 1 enhancement
|
3
|
+
* doppelte Tags in Datei nicht mehrfach zaehlen
|
4
|
+
* photocase: bessere Fehleranalyse, falls Upload-Fehler
|
5
|
+
* 1 bugfix
|
6
|
+
* Anpassung an neue Syntax beim mechanize gem in site_* Dateien
|
7
|
+
|
1
8
|
== 0.8.0 2008-06-28
|
2
9
|
* 1 major enhancement
|
3
10
|
* neue Site Bigstockphoto erstellt
|
@@ -9,9 +16,11 @@
|
|
9
16
|
* Optimierungen in bin/multistockphoto
|
10
17
|
* 1 bug fix
|
11
18
|
* --stats Fehler bei deaktivierten Sites behoben
|
19
|
+
|
12
20
|
== 0.7.1 2008-06-16
|
13
21
|
* 1 bug fix
|
14
22
|
* entfernen von ueberfluessiger aldi-site fuehrte anderweitig zu Fehler
|
23
|
+
|
15
24
|
== 0.7.0 2008-06-16
|
16
25
|
* 1 major enhancement
|
17
26
|
* neue Site Dreamstime erstellt
|
data/Manifest.txt
CHANGED
@@ -22,6 +22,7 @@ lib/multistockphoto/mock_zoonar.rb
|
|
22
22
|
lib/multistockphoto/site_photocase.rb
|
23
23
|
lib/multistockphoto/site_dreamstime.rb
|
24
24
|
lib/multistockphoto/site_bigstockphoto.rb
|
25
|
+
lib/multistockphoto/site_panthermedia.rb
|
25
26
|
lib/multistockphoto/photo.rb
|
26
27
|
lib/multistockphoto/upload_exception.rb
|
27
28
|
script/console
|
data/bin/multistockphoto
CHANGED
@@ -12,13 +12,16 @@ include Grep
|
|
12
12
|
SENDELISTE = 'sendeliste.dat'
|
13
13
|
ROT_PREFIX = 'rot_'
|
14
14
|
MAX_ERRORS = 3
|
15
|
-
PICTURE_FILES = /(.PNG|.JPG|.GIF|.
|
15
|
+
PICTURE_FILES = /(.PNG|.JPG|.JPEG|.GIF|.EPS|.AI|.PSD|.PDF|.TIF|.TIFF)$/
|
16
16
|
|
17
17
|
require 'fcntl'
|
18
18
|
|
19
19
|
#STDOUT.sync = true
|
20
20
|
#STDOUT.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK)
|
21
21
|
|
22
|
+
require 'logger'
|
23
|
+
$log = Logger.new('multistockphoto.log', 'daily', 30)
|
24
|
+
|
22
25
|
# alle Sites komplett bearbeitet?
|
23
26
|
def all_sites_done(sites,n)
|
24
27
|
#p sites
|
@@ -165,6 +168,15 @@ Choice.options do
|
|
165
168
|
end
|
166
169
|
end
|
167
170
|
|
171
|
+
option :silent do
|
172
|
+
short '-c'
|
173
|
+
long '--silent'
|
174
|
+
desc 'No output at list-done and purge-done'
|
175
|
+
action do
|
176
|
+
$silent = true
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
168
180
|
option :ordered do
|
169
181
|
short '-o' #TODO: geht das?
|
170
182
|
long '--ordered'
|
@@ -186,9 +198,9 @@ Choice.options do
|
|
186
198
|
end
|
187
199
|
|
188
200
|
option :count do
|
189
|
-
short '-
|
201
|
+
short '-b'
|
190
202
|
long '--count'
|
191
|
-
desc '
|
203
|
+
desc 'Count - only photo with at least N tags'
|
192
204
|
cast Integer
|
193
205
|
default 0
|
194
206
|
action do
|
@@ -197,14 +209,32 @@ Choice.options do
|
|
197
209
|
end
|
198
210
|
|
199
211
|
option :plist do
|
200
|
-
short '-
|
212
|
+
short '-d'
|
201
213
|
long '--plist'
|
202
|
-
desc 'Show
|
214
|
+
desc 'Show detailed sending state for each picture'
|
203
215
|
action do
|
204
216
|
$plist = true
|
205
217
|
end
|
206
218
|
end
|
207
219
|
|
220
|
+
option :check_translation do
|
221
|
+
short '-e'
|
222
|
+
long '--check-translation'
|
223
|
+
desc 'Checks if translation can be done for all tags'
|
224
|
+
action do
|
225
|
+
$check_translation = true
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
option :shuffle do
|
230
|
+
short '-f'
|
231
|
+
long '--shuffle'
|
232
|
+
desc 'Shuffle order of files (sending)'
|
233
|
+
action do
|
234
|
+
$shuffle = true
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
208
238
|
option :dont_send do
|
209
239
|
long '--dont-send'
|
210
240
|
action do
|
@@ -243,7 +273,7 @@ def not_enough_tags?(photo)
|
|
243
273
|
else
|
244
274
|
if $count_flag
|
245
275
|
photo.set_keywords
|
246
|
-
if photo.tags.size < $count
|
276
|
+
if photo.tags.uniq.size < $count
|
247
277
|
return true
|
248
278
|
end
|
249
279
|
end
|
@@ -284,6 +314,9 @@ def send_all
|
|
284
314
|
if $ordered
|
285
315
|
allfiles.sort!
|
286
316
|
end
|
317
|
+
if $shuffle
|
318
|
+
allfiles = allfiles.sort_by { rand }
|
319
|
+
end
|
287
320
|
allfiles.each {|filename|
|
288
321
|
next if File.basename(filename)[0,4] == ROT_PREFIX
|
289
322
|
#puts "rot_ uebersprungen"
|
@@ -291,69 +324,96 @@ def send_all
|
|
291
324
|
total_per_site_flag = $total_per_site
|
292
325
|
photo = Photo.new(filename)
|
293
326
|
next if not_enough_tags? photo
|
327
|
+
# threads = []
|
294
328
|
$active_sites.each {|site_name|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
329
|
+
# TODO:
|
330
|
+
# Bevor man Threads nutzt, muß man sicherstellen, dass das Rotieren
|
331
|
+
# von Bildern abeschlossen ist, bevor ein anderer Thread auf das
|
332
|
+
# rotierte Bilde zugreift
|
333
|
+
#threads << Thread.new(site_name) { |site_name|
|
334
|
+
#print "Thread #{site_name} gestartet\n"
|
335
|
+
#Thread.current["threadname"] = site_name.to_s
|
336
|
+
begin
|
337
|
+
Timeout::timeout(10.0*60.0) do
|
338
|
+
t1 = Time.now
|
339
|
+
site = eval(site_name.to_s.capitalize+".new(#{site_name.to_s.capitalize})")
|
340
|
+
done[site_name] = true if remaining[site_name] <= 0
|
341
|
+
next if done[site_name]
|
306
342
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
if
|
327
|
-
|
328
|
-
|
329
|
-
|
343
|
+
if ! site.accept?(File.extname(filename))
|
344
|
+
warn "Warning: Site (#{site_name}) does not support this file extension (#{File.extname(filename)}). Skipped"
|
345
|
+
next
|
346
|
+
end
|
347
|
+
if ! site.photosize_valid?(photo)
|
348
|
+
warn "Warning: filesize for #{photo.filename} not valid for site #{site_name}"
|
349
|
+
next
|
350
|
+
end
|
351
|
+
#end
|
352
|
+
if $send_all or
|
353
|
+
(total_per_site_flag and
|
354
|
+
total[site_name] < Choice.choices[:total_per_site])
|
355
|
+
if !site.already_sent?(photo)
|
356
|
+
begin
|
357
|
+
result = site.transfer(photo,$dont_send,$dont_log)
|
358
|
+
if result != :duplicate
|
359
|
+
total_transfers += 1
|
360
|
+
total[site_name] += 1
|
361
|
+
remaining[site_name] -= 1
|
362
|
+
done[site_name] = true if remaining[site_name] <= 0
|
363
|
+
errors[site_name] = 0
|
364
|
+
t2 = Time.now
|
365
|
+
if $verbose
|
366
|
+
if $dont_send and $dont_log
|
367
|
+
puts "1:23"
|
368
|
+
else
|
369
|
+
puts formatted_minutes(t2-t1)
|
370
|
+
end
|
371
|
+
p total # Summenzeile ausgeben
|
330
372
|
end
|
331
|
-
p total # Summenzeile ausgeben
|
332
373
|
end
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
374
|
+
# TODO: SocketError nur experimentell
|
375
|
+
rescue SocketError
|
376
|
+
print "SocketError - ignore and sleep 30 seconds"
|
377
|
+
sleep 30
|
378
|
+
errors[site_name] += 1
|
379
|
+
if errors[site_name] >= MAX_ERRORS
|
380
|
+
done[site_name] = true
|
381
|
+
puts "too many errors. giving up."
|
382
|
+
end
|
383
|
+
puts "Upload error at sending to #{site_name} occurred. Check configuration or try again later!"
|
384
|
+
rescue UploadException
|
385
|
+
errors[site_name] += 1
|
386
|
+
if errors[site_name] >= MAX_ERRORS
|
387
|
+
done[site_name] = true
|
388
|
+
puts "too many errors. giving up."
|
389
|
+
end
|
390
|
+
puts "Upload error at sending to #{site_name} occurred. Check configuration or try again later!"
|
391
|
+
rescue Timeout::Error
|
392
|
+
puts "timeout error"
|
393
|
+
#timeout error
|
394
|
+
errors[site_name] += 1
|
395
|
+
if errors[site_name] >= MAX_ERRORS
|
396
|
+
done[site_name] = true
|
397
|
+
puts "too many errors. giving up."
|
398
|
+
end
|
348
399
|
end
|
349
400
|
end
|
401
|
+
break if result != :duplicate && total_transfers >= Choice.choices[:total_transfers]
|
402
|
+
break if all_sites_done(total,Choice.choices[:total_per_site])
|
350
403
|
end
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
404
|
+
end # Timeout
|
405
|
+
end # timeout
|
406
|
+
#} # Thread
|
407
|
+
|
408
|
+
} # alle Sites
|
409
|
+
#print "warte auf Beendigung aller Threads ...\n"
|
410
|
+
#
|
411
|
+
#threads.each {|thr|
|
412
|
+
# print "warte auf Thread "+thr.to_s+" "+thr["threadname"]+"\n"
|
413
|
+
# $stdout.flush
|
414
|
+
# thr.join
|
415
|
+
#}
|
416
|
+
|
357
417
|
end
|
358
418
|
}
|
359
419
|
puts "#{total_transfers} photos sent."
|
@@ -429,7 +489,7 @@ def stats
|
|
429
489
|
end
|
430
490
|
}
|
431
491
|
}
|
432
|
-
print " "*
|
492
|
+
print " "*14+"| "
|
433
493
|
$active_sites.each {|site|
|
434
494
|
print "#{site.to_s}"
|
435
495
|
print " | "
|
@@ -440,7 +500,11 @@ def stats
|
|
440
500
|
10.times do |i|
|
441
501
|
chosen_date = today - i
|
442
502
|
total = 0
|
443
|
-
print chosen_date.to_s+" | "
|
503
|
+
# print chosen_date.to_s+" | "
|
504
|
+
print sprintf("%2s %04d-%02d-%02d",%w{ So Mo Di Mi Do Fr Sa}[chosen_date.wday],
|
505
|
+
chosen_date.year,
|
506
|
+
chosen_date.month,
|
507
|
+
chosen_date.day)+" | "
|
444
508
|
$active_sites.each {|site|
|
445
509
|
key = [site.to_s, chosen_date]
|
446
510
|
fmt = "%#{site.to_s.length}d | "
|
@@ -463,7 +527,7 @@ def stats
|
|
463
527
|
end
|
464
528
|
all += v
|
465
529
|
}
|
466
|
-
print "total
|
530
|
+
print "total | "
|
467
531
|
$active_sites.each {|site|
|
468
532
|
fmt = "%#{site.to_s.length}d | "
|
469
533
|
printf(fmt, (totals[site] || 0))
|
@@ -510,7 +574,7 @@ def list_done
|
|
510
574
|
}
|
511
575
|
end
|
512
576
|
unless photo_sent.has_value?(false)
|
513
|
-
puts "#{filename} sent to all sites"
|
577
|
+
puts "#{filename} sent to all sites"+" "*20 unless $silent
|
514
578
|
end
|
515
579
|
}
|
516
580
|
end
|
@@ -532,7 +596,7 @@ end
|
|
532
596
|
# dort koennen sie entweder geloescht oder archiviert werden
|
533
597
|
# Es schadet aber auch nicht, wenn dieser Schritt nie ausgefuehrt wird.
|
534
598
|
def purge_done
|
535
|
-
puts 'purge_done'
|
599
|
+
puts 'purge_done' unless $silent
|
536
600
|
Dir.glob(File.join($upload_dir,'*')).each {|filename|
|
537
601
|
photo_sent = {}
|
538
602
|
$active_sites.each {|site|
|
@@ -549,15 +613,16 @@ def purge_done
|
|
549
613
|
}
|
550
614
|
end
|
551
615
|
unless photo_sent.has_value?(false)
|
552
|
-
puts "#{filename} sent to all sites"
|
616
|
+
puts "#{filename} sent to all sites" unless $silent
|
553
617
|
if !File.exist? $done_dir
|
554
618
|
FileUtils.mkdir $done_dir
|
555
619
|
end
|
556
|
-
puts "mv #{filename} done/#{File.basename filename}"
|
557
|
-
FileUtils.mv
|
620
|
+
puts "mv #{filename} done/#{File.basename filename}" unless $silent
|
621
|
+
FileUtils.mv(filename, File.join($done_dir,File.basename(filename)))
|
558
622
|
if tagsfile?(filename)
|
559
|
-
puts "mv #{tagsfilename(filename)} done/#{File.basename(tagsfilename(filename))}"
|
560
|
-
FileUtils.mv(tagsfilename(filename),
|
623
|
+
puts "mv #{tagsfilename(filename)} done/#{File.basename(tagsfilename(filename))}" unless $silent
|
624
|
+
FileUtils.mv(tagsfilename(filename),
|
625
|
+
File.join($done_dir,File.basename(tagsfilename(filename))))
|
561
626
|
end
|
562
627
|
end
|
563
628
|
}
|
@@ -582,8 +647,8 @@ def no_tags
|
|
582
647
|
if $count_flag
|
583
648
|
photo = Photo.new(fn)
|
584
649
|
photo.set_keywords_without_write
|
585
|
-
if photo.tags.size < $count
|
586
|
-
puts fn + " only #{photo.tags.size} keywords"
|
650
|
+
if photo.tags.uniq.size < $count
|
651
|
+
puts fn + " only #{photo.tags.uniq.size} keywords"
|
587
652
|
end
|
588
653
|
end
|
589
654
|
end
|
@@ -609,7 +674,7 @@ def plist
|
|
609
674
|
photo.set_keywords_without_write
|
610
675
|
printf("%-30s ",filename)
|
611
676
|
if tagsfile?(filename)
|
612
|
-
printf "(t %2d) ", photo.tags.size
|
677
|
+
printf "(t %2d) ", photo.tags.uniq.size
|
613
678
|
else
|
614
679
|
print ' '
|
615
680
|
end
|
@@ -626,6 +691,24 @@ def plist
|
|
626
691
|
}
|
627
692
|
end
|
628
693
|
|
694
|
+
# Testet, ob für jedes Tag in jeder tags-Datei eine Übersetzung existiert.
|
695
|
+
def check_translation
|
696
|
+
allfiles = Dir.glob(File.join($upload_dir,'*'))
|
697
|
+
if $ordered
|
698
|
+
allfiles.sort!
|
699
|
+
end
|
700
|
+
allfiles.each {|fn|
|
701
|
+
next if File.basename(fn)[0,4] == ROT_PREFIX
|
702
|
+
if fn.upcase =~ PICTURE_FILES
|
703
|
+
if tagsfile?(fn)
|
704
|
+
photo = Photo.new(fn)
|
705
|
+
photo.set_keywords_without_write(:de,:en) #TODO: später noch flexibler machen!
|
706
|
+
end
|
707
|
+
|
708
|
+
end
|
709
|
+
}
|
710
|
+
end
|
711
|
+
|
629
712
|
if $send_all or $total_per_site
|
630
713
|
send_all
|
631
714
|
end
|
@@ -662,3 +745,7 @@ end
|
|
662
745
|
if $plist
|
663
746
|
plist
|
664
747
|
end
|
748
|
+
|
749
|
+
if $check_translation
|
750
|
+
check_translation
|
751
|
+
end
|
data/bin/multistockphoto-gui
CHANGED
@@ -5,6 +5,9 @@ begin
|
|
5
5
|
require 'rubygems'
|
6
6
|
rescue LoadError
|
7
7
|
end
|
8
|
+
#gem 'wxruby', "1.9.7"
|
9
|
+
#require 'wx'
|
10
|
+
gem 'wxruby', "!=1.9.8"
|
8
11
|
require 'wx'
|
9
12
|
|
10
13
|
# This sample shows a fairly minimal Wx::App using a Frame, with a
|
@@ -19,6 +22,7 @@ class MinimalFrame < Wx::Frame
|
|
19
22
|
Id_purge_done = 2003
|
20
23
|
Id_no_tags = 2004
|
21
24
|
Id_not_sent = 2005
|
25
|
+
Id_random = 2006
|
22
26
|
|
23
27
|
def initialize(title)
|
24
28
|
# The main application frame has no parent (nil)
|
@@ -43,6 +47,7 @@ class MinimalFrame < Wx::Frame
|
|
43
47
|
menu_actions.append(Id_send_all,"Send all photos")
|
44
48
|
menu_actions.append(Id_list_done,"List all done photos")
|
45
49
|
menu_actions.append(Id_purge_done,"Purge all done photos")
|
50
|
+
menu_actions.append(Id_random,"Random")
|
46
51
|
menu_bar.append(menu_actions, "Actions")
|
47
52
|
|
48
53
|
#multistockphoto - admin
|
@@ -70,6 +75,7 @@ class MinimalFrame < Wx::Frame
|
|
70
75
|
evt_menu Id_list_done, :on_list_done
|
71
76
|
evt_menu Id_no_tags, :on_no_tags
|
72
77
|
evt_menu Id_not_sent, :on_not_sent
|
78
|
+
evt_menu Id_random, :on_random
|
73
79
|
end
|
74
80
|
|
75
81
|
# End the application; it should finish automatically when the last
|
@@ -96,6 +102,35 @@ class MinimalFrame < Wx::Frame
|
|
96
102
|
self.status_text = "done uploading all photos"
|
97
103
|
end
|
98
104
|
|
105
|
+
def on_random
|
106
|
+
puts 'random'
|
107
|
+
self.status_text = "select a random picture"
|
108
|
+
all = Dir.glob("upload/*.JPG")
|
109
|
+
while true
|
110
|
+
r = all[rand(all.size)]
|
111
|
+
b = File.basename(r,".JPG")
|
112
|
+
p b
|
113
|
+
t = File.join("upload", b)+".tags"
|
114
|
+
p t
|
115
|
+
if b[0...4] == 'rot_'
|
116
|
+
puts "nur rotierte Datei"
|
117
|
+
elsif File.exist? t
|
118
|
+
puts "tags-File existiert schon"
|
119
|
+
else
|
120
|
+
Thread.start do
|
121
|
+
`eog #{r}`
|
122
|
+
end
|
123
|
+
Thread.start do
|
124
|
+
`gedit #{t}`
|
125
|
+
end
|
126
|
+
break # wir haben eine gefunden
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# puts `ruby random.rb`
|
131
|
+
self.status_text = "done random"
|
132
|
+
end
|
133
|
+
|
99
134
|
def on_list_done
|
100
135
|
self.status_text = "list-done"
|
101
136
|
result = `multistockphoto --list-done`
|
@@ -149,4 +184,4 @@ Wx::App.run do
|
|
149
184
|
self.app_name = 'Minimal'
|
150
185
|
frame = MinimalFrame.new("multistockphoto")
|
151
186
|
frame.show
|
152
|
-
end
|
187
|
+
end
|
data/config.yaml
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
- :photocase
|
6
6
|
- :dreamstime
|
7
7
|
- :bigstockphoto
|
8
|
+
- :panthermedia
|
8
9
|
:zoonar:
|
9
10
|
:user: 'hugo'
|
10
11
|
:password: 'lalala'
|
@@ -31,3 +32,9 @@
|
|
31
32
|
:ftp_user: 'dennis12345'
|
32
33
|
:ftp_password: 'ftpgeheimdennis'
|
33
34
|
:total_per_day: 1
|
35
|
+
:panthermedia:
|
36
|
+
:user: 'iris'
|
37
|
+
:password: 'irisgeheim'
|
38
|
+
:ftp_user: 'irisftp'
|
39
|
+
:ftp_password: 'irisftpgeheim'
|
40
|
+
:total_per_day: 1de
|
@@ -8,6 +8,10 @@ class GenericSite
|
|
8
8
|
def initialize(name)
|
9
9
|
@name = name
|
10
10
|
@max_errors = MAX_ERRORS
|
11
|
+
unless File.exist?(SENDLIST)
|
12
|
+
warn "File #{SENDLIST} does not exist - creating empty file"
|
13
|
+
File.open(SENDLIST,"w"){|file|}
|
14
|
+
end
|
11
15
|
end
|
12
16
|
|
13
17
|
def photos_fuer_heute_uebrig?
|
@@ -30,6 +34,11 @@ class GenericSite
|
|
30
34
|
return g.size > 0
|
31
35
|
end
|
32
36
|
|
37
|
+
# Groesse der Datei gueltig fuer die Site, auch in Verbindung mit File-Typ
|
38
|
+
def photosize_valid?(photo)
|
39
|
+
true
|
40
|
+
end
|
41
|
+
|
33
42
|
protected
|
34
43
|
|
35
44
|
def sent_today_site(p_site)
|
@@ -37,8 +46,11 @@ class GenericSite
|
|
37
46
|
count = {}
|
38
47
|
File.open(SENDLIST) {|f|
|
39
48
|
f.each_line {|line|
|
49
|
+
#TODO: leere Zeilen ignorieren
|
40
50
|
site, photo_file, time = line.chomp.split("\t")
|
41
|
-
|
51
|
+
|
52
|
+
date = Date.parse(time)
|
53
|
+
|
42
54
|
key = [site, date]
|
43
55
|
if count[key]==nil
|
44
56
|
count[key] = 1
|