multistockphoto 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,14 @@
1
+ == 0.8.0 2008-06-28
2
+ * 1 major enhancement
3
+ * neue Site Bigstockphoto erstellt
4
+ * Option --plist in bin/multistockphoto erstellt
5
+ * Option --count=N in Verbindung mit --no-tags und --tags_only erstellt
6
+ * 1 minor enhancement
7
+ * Absicherung/Fehlermeldung wenn Config-Datei nicht richtig gelesen werden kann
8
+ * Methode self.accept?/accept? je Site Klasse neu, fuer zulaessige File-Typen
9
+ * Optimierungen in bin/multistockphoto
10
+ * 1 bug fix
11
+ * --stats Fehler bei deaktivierten Sites behoben
1
12
  == 0.7.1 2008-06-16
2
13
  * 1 bug fix
3
14
  * entfernen von ueberfluessiger aldi-site fuehrte anderweitig zu Fehler
data/Manifest.txt CHANGED
@@ -21,6 +21,7 @@ lib/multistockphoto/site_zoonar.rb
21
21
  lib/multistockphoto/mock_zoonar.rb
22
22
  lib/multistockphoto/site_photocase.rb
23
23
  lib/multistockphoto/site_dreamstime.rb
24
+ lib/multistockphoto/site_bigstockphoto.rb
24
25
  lib/multistockphoto/photo.rb
25
26
  lib/multistockphoto/upload_exception.rb
26
27
  script/console
data/bin/multistockphoto CHANGED
@@ -6,10 +6,18 @@ require 'multistockphoto'
6
6
  require 'RMagick'
7
7
  include Magick
8
8
  require 'exifr'
9
+ require 'grep'
10
+ include Grep
9
11
 
10
12
  SENDELISTE = 'sendeliste.dat'
13
+ ROT_PREFIX = 'rot_'
11
14
  MAX_ERRORS = 3
12
- PICTURE_FILES = /.PNG|.JPG|.GIF|.JPEG/
15
+ PICTURE_FILES = /(.PNG|.JPG|.GIF|.JPEG|.EPS|.AI|.PSD|.PDF|.TIF|.TIFF)$/
16
+
17
+ require 'fcntl'
18
+
19
+ #STDOUT.sync = true
20
+ #STDOUT.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK)
13
21
 
14
22
  # alle Sites komplett bearbeitet?
15
23
  def all_sites_done(sites,n)
@@ -170,11 +178,33 @@ Choice.options do
170
178
  short '-q'
171
179
  long '--no-tags'
172
180
  desc 'List photos without tags file' #TODO: eigentlich muss man auch auf vorhandene IPTC in Photo selbst testen
181
+ #cast Integer
182
+ #default 0
173
183
  action do
174
184
  $no_tags = true
175
185
  end
176
186
  end
177
187
 
188
+ option :count do
189
+ short '-3'
190
+ long '--count'
191
+ desc 'count'
192
+ cast Integer
193
+ default 0
194
+ action do
195
+ $count_flag = true
196
+ end
197
+ end
198
+
199
+ option :plist do
200
+ short '-2'
201
+ long '--plist'
202
+ desc 'Show details sending status for each picture'
203
+ action do
204
+ $plist = true
205
+ end
206
+ end
207
+
178
208
  option :dont_send do
179
209
  long '--dont-send'
180
210
  action do
@@ -200,51 +230,49 @@ def formatted_minutes(d)
200
230
  mm = (d / 60.0).floor
201
231
  d = d-mm*60
202
232
  ss = d.round
203
- # return sprintf("%02d:%02d:%02d",hh,mm,ss)
204
233
  return sprintf("%d:%02d",mm,ss)
205
234
  end
206
235
 
236
+ # Hilfsmethode fuer send_all
237
+ # false, falls keine Tags-Datei existiert oder laut Aufrufparameter --count zu wenige
238
+ # Keywords hat.
239
+ def not_enough_tags?(photo)
240
+ if $tags_only
241
+ if ! tagsfile?(photo.filename)
242
+ return true
243
+ else
244
+ if $count_flag
245
+ photo.set_keywords
246
+ if photo.tags.size < $count
247
+ return true
248
+ end
249
+ end
250
+ end
251
+ end # tags_only
252
+ false
253
+ end
254
+
207
255
  # Senden aller noch nicht gesendeten Photos an alle Sites
208
256
  # unter Berücksichtigung sonstiger Optionsparameter
209
257
  def send_all
258
+ if $count_flag
259
+ $count = Choice.choices[:count]
260
+ end
210
261
  total_transfers = 0
211
262
  total = {}
212
263
  errors = {}
213
- #sites = [:fotolia, :zoonar, :photocase]
214
264
  $active_sites.each {|site|
215
265
  total[site] = 0
216
266
  errors[site] = 0
217
267
  }
218
268
  puts 'sending all unsent photos'
219
269
 
220
- # rotate photos
221
- # - true: we do the rotation
222
- # - false: the site does the rotation automatically
223
- # change to false if a photo site can automatically rotate photos
224
- # rotate_photos = {
225
- # :fotolia => true,
226
- # :zoonar => true,
227
- # :photocase => true,
228
- # }
229
-
230
- # done = {
231
- # :fotolia => false,
232
- # :zoonar => false,
233
- # :photocase => false,
234
- # :dreamstime => false,
235
- # }
236
- # remaining = {
237
- # :fotolia => 999_999_999,
238
- # :zoonar => 999_999_999,
239
- # :photocase => 999_999_999,
240
- # :dreamstime => 999_999_999,
241
- # }
242
270
  remaining = Hash.new
243
271
  done = Hash.new
244
272
  $active_sites.each {|site|
245
273
  done[site] = false
246
274
  remaining[site] = 999_999_999
247
- }
275
+ }
248
276
  $active_sites.each {|site|
249
277
  # dynamisches Site.new("site")
250
278
  s = eval("#{site.to_s.capitalize}"+".new(#{site.to_s.capitalize})")
@@ -257,25 +285,30 @@ def send_all
257
285
  allfiles.sort!
258
286
  end
259
287
  allfiles.each {|filename|
260
- next if File.basename(filename)[0,4] == 'rot_'
288
+ next if File.basename(filename)[0,4] == ROT_PREFIX
261
289
  #puts "rot_ uebersprungen"
262
290
  if filename.upcase =~ PICTURE_FILES
263
291
  total_per_site_flag = $total_per_site
264
292
  photo = Photo.new(filename)
293
+ next if not_enough_tags? photo
265
294
  $active_sites.each {|site_name|
266
295
  begin
267
296
  Timeout::timeout(10.0*60.0) do
268
297
  t1 = Time.now
269
298
  site = eval(site_name.to_s.capitalize+".new(#{site_name.to_s.capitalize})")
299
+ # if ! site.accept?(File.extname(filename))
300
+ # warn "Warning: Site (#{site_name}) does not support this file extension (#{File.extname(filename)}). Skipped"
301
+ # next
302
+ # end
270
303
  # site.rotate_photos = rotate_photos[site_name]
271
304
  done[site_name] = true if remaining[site_name] <= 0
272
305
  next if done[site_name]
273
- if $tags_only and ! tagsfile?(photo.filename)
274
- # if $verbose
275
- # puts "#{photo.filename}: no tags file. skipped."
276
- # end
277
- next
306
+
307
+ if ! site.accept?(File.extname(filename))
308
+ warn "Warning: Site (#{site_name}) does not support this file extension (#{File.extname(filename)}). Skipped"
309
+ next
278
310
  end
311
+ #end
279
312
  if $send_all or
280
313
  (total_per_site_flag and
281
314
  total[site_name] < Choice.choices[:total_per_site])
@@ -288,8 +321,8 @@ def send_all
288
321
  remaining[site_name] -= 1
289
322
  done[site_name] = true if remaining[site_name] <= 0
290
323
  errors[site_name] = 0
324
+ t2 = Time.now
291
325
  if $verbose
292
- t2 = Time.now
293
326
  if $dont_send and $dont_log
294
327
  puts "1:23"
295
328
  else
@@ -298,17 +331,13 @@ def send_all
298
331
  p total # Summenzeile ausgeben
299
332
  end
300
333
  end
301
- rescue UploadException #Net::FTPTempError #TODO: gefaellt mir noch nicht so ganz
334
+ rescue UploadException
302
335
  errors[site_name] += 1
303
336
  if errors[site_name] >= MAX_ERRORS
304
- done[site_name] = true # nichts mehr uploaden
337
+ done[site_name] = true
305
338
  puts "too many errors. giving up."
306
339
  end
307
- # puts "FTP-Fehler beim Senden an #{site_name} aufgetreten. Ggf. Konfiguration ueberpruefen oder spaeter erneut vesuchen"
308
340
  puts "Upload error at sending to #{site_name} occurred. Check configuration or try again later!"
309
- # rescue SocketError
310
- # puts "SocketError: no net? waiting 60 secs"
311
- # sleep 60
312
341
  rescue Timeout::Error
313
342
  puts "timeout error"
314
343
  #timeout error
@@ -367,7 +396,7 @@ def not_sent
367
396
  }
368
397
  puts "not sent photos:"
369
398
  Dir.glob(File.join($upload_dir,'*')).each {|filename|
370
- next if File.basename(filename)[0,4] == 'rot_'
399
+ next if File.basename(filename)[0,4] == ROT_PREFIX
371
400
  if filename.upcase =~ PICTURE_FILES
372
401
  $active_sites.each {|site|
373
402
  print filename+"\t"+site.to_s+"\t" if $verbose
@@ -428,7 +457,10 @@ def stats
428
457
  }
429
458
  count.each_pair {|k,v|
430
459
  site = k[0]
431
- totals[site.to_sym] += v
460
+ if $active_sites.include? site.to_sym # sonst kommt es zu Fehlern bei
461
+ # deaktivierten Sites
462
+ totals[site.to_sym] += v
463
+ end
432
464
  all += v
433
465
  }
434
466
  print "total | "
@@ -445,7 +477,7 @@ def stats
445
477
  count[:tags] = 0
446
478
  count[:notags] = 0
447
479
  Dir.glob(File.join($upload_dir,'*')).each {|filename|
448
- next if File.basename(filename)[0,4] == 'rot_'
480
+ next if File.basename(filename)[0,4] == ROT_PREFIX
449
481
  if filename.upcase =~ PICTURE_FILES
450
482
  if tagsfile?(filename)
451
483
  count[:tags] += 1
@@ -467,7 +499,7 @@ def list_done
467
499
  $active_sites.each {|site|
468
500
  photo_sent[site] = false
469
501
  }
470
- next if File.basename(filename)[0,4] == 'rot_'
502
+ next if File.basename(filename)[0,4] == ROT_PREFIX
471
503
  if filename.upcase =~ PICTURE_FILES
472
504
  $active_sites.each {|site|
473
505
  print filename+"\t"+site.to_s+"\t" if $verbose
@@ -506,7 +538,7 @@ def purge_done
506
538
  $active_sites.each {|site|
507
539
  photo_sent[site] = false
508
540
  }
509
- next if File.basename(filename)[0,4] == 'rot_'
541
+ next if File.basename(filename)[0,4] == ROT_PREFIX
510
542
  if filename.upcase =~ PICTURE_FILES
511
543
  $active_sites.each {|site|
512
544
  print filename+"\t"+site.to_s+"\t" if $verbose
@@ -533,20 +565,63 @@ end
533
565
 
534
566
  # Listet alle Photo-Dateien auf, zu denen es keine .tags-Datei gibt
535
567
  def no_tags
536
- sender = Sender.new("Thomas")
568
+ if $count_flag
569
+ $count = Choice.choices[:count]
570
+ end
537
571
  allfiles = Dir.glob(File.join($upload_dir,'*'))
538
572
  if $ordered
539
573
  allfiles.sort!
540
574
  end
541
575
  allfiles.each {|fn|
542
- next if File.basename(fn)[0,4] == 'rot_'
576
+ next if File.basename(fn)[0,4] == ROT_PREFIX
543
577
  if fn.upcase =~ PICTURE_FILES
544
578
  if ! tagsfile?(fn)
545
- #if ! sender.sent_to?(fn,:fotolia) #TODO: provisorisch
546
- puts fn
547
- #end
548
-
579
+ puts fn
580
+ else
581
+ # es gibt eine aber zu wenige keywords
582
+ if $count_flag
583
+ photo = Photo.new(fn)
584
+ photo.set_keywords_without_write
585
+ if photo.tags.size < $count
586
+ puts fn + " only #{photo.tags.size} keywords"
587
+ end
588
+ end
589
+ end
590
+
591
+ end
592
+ }
593
+ end
594
+
595
+ def plist
596
+ allfiles = Dir.glob(File.join($upload_dir,'*'))
597
+ if $ordered
598
+ allfiles.sort!
599
+ end
600
+ print ' '+' '*34
601
+ $active_sites.each {|site|
602
+ printf("%-10s",site)
603
+ }
604
+ puts
605
+ allfiles.each {|filename|
606
+ next if File.basename(filename)[0,4] == ROT_PREFIX
607
+ if filename.upcase =~ PICTURE_FILES
608
+ photo = Photo.new(filename)
609
+ photo.set_keywords_without_write
610
+ printf("%-30s ",filename)
611
+ if tagsfile?(filename)
612
+ printf "(t %2d) ", photo.tags.size
613
+ else
614
+ print ' '
549
615
  end
616
+ $active_sites.each {|site|
617
+ g = grep(SENDELISTE, /#{site.to_s}\t#{filename}/)
618
+ if g.size == 0
619
+ print ' '*10
620
+ else
621
+ print '+'+' '*9
622
+ end
623
+ }
624
+ puts
550
625
  end
551
626
  }
552
627
  end
@@ -584,3 +659,6 @@ if $no_tags
584
659
  no_tags
585
660
  end
586
661
 
662
+ if $plist
663
+ plist
664
+ end
data/config.yaml CHANGED
@@ -4,6 +4,7 @@
4
4
  - :zoonar
5
5
  - :photocase
6
6
  - :dreamstime
7
+ - :bigstockphoto
7
8
  :zoonar:
8
9
  :user: 'hugo'
9
10
  :password: 'lalala'
@@ -24,3 +25,9 @@
24
25
  :ftp_user: 'martha12345'
25
26
  :ftp_password: 'ftpgeheimmartha'
26
27
  :total_per_day: 1
28
+ :bigstockphoto:
29
+ :user: 'dennis'
30
+ :password: 'dennisgeheim'
31
+ :ftp_user: 'dennis12345'
32
+ :ftp_password: 'ftpgeheimdennis'
33
+ :total_per_day: 1
@@ -50,30 +50,56 @@ class Photo
50
50
  pic.auto_orient!
51
51
  puts "rotating picture, writing to #{tmpname}"
52
52
  pic.write(tmpname)
53
-
54
53
  end
55
54
  @rotated_filename = tmpname
56
55
  end
57
56
  end
58
57
 
59
58
  # setzt IPTC-Keywords, enthalten in Datei <imagefilename>.tags
60
- def set_keywords
59
+ # wenn from_lang und to_lang uebergeben, dann Uebersetzung der Keywords
60
+ # z.B. set_keywords(:de,:en)
61
+ def set_keywords(from_lang=nil,to_lang=nil)
61
62
  ext = File.extname(@filename)
63
+ if ext.upcase != '.JPG' and ext.upcase != '.JPEG'
64
+ warn "cannot set Exif-Header with non-JPEG file #{@filename}"
65
+ return
66
+ end
62
67
  fn = @filename.sub(ext,".tags")
63
68
  lines=[]
64
69
  begin
65
70
  File.open(fn) {|f|
66
71
  lines = f.read
67
72
  }
68
- puts "tags file #{fn} found"
69
73
  tags = Photo.to_tags(lines)
74
+ if from_lang and to_lang
75
+ tags = translate_tags(tags,from_lang,to_lang)
76
+ end
70
77
  self.tags = tags
71
- print "writing tags to file ... "
72
- $stdout.flush
78
+ #print "writing tags to file ... "
79
+ #$stdout.flush
73
80
  write_keywords
74
- puts "done"
81
+ #puts "done"
75
82
  rescue Errno::ENOENT
76
- warn "WARNING: no tags file #{fn}"
83
+ #warn "WARNING: no tags file #{fn}"
84
+ end
85
+ end
86
+
87
+ def set_keywords_without_write(from_lang=nil,to_lang=nil)
88
+ ext = File.extname(@filename)
89
+ fn = @filename.sub(ext,".tags")
90
+ lines=[]
91
+ begin
92
+ File.open(fn) {|f|
93
+ lines = f.read
94
+ }
95
+ #puts "tags file #{fn} found"
96
+ tags = Photo.to_tags(lines)
97
+ if from_lang and to_lang
98
+ tags = translate_tags(tags,from_lang,to_lang)
99
+ end
100
+ self.tags = tags
101
+ rescue Errno::ENOENT
102
+ #warn "WARNING: no tags file #{fn}"
77
103
  end
78
104
  end
79
105
 
@@ -82,15 +108,6 @@ class Photo
82
108
  tags
83
109
  end
84
110
 
85
- # erstellt Key-Array aus String (getrennt durch newline,',' oder ' ')
86
- def self.to_tags(s)
87
- keys = []
88
- s.split(/[, |\t\n]+/).each {|w|
89
- keys << w
90
- }
91
- keys
92
- end
93
-
94
111
  # ermittelt Keywords aus Datei
95
112
  def file_keywords
96
113
  photo = MiniExiftool.new @filename
@@ -101,18 +118,20 @@ class Photo
101
118
  # schreibt Keywords in Datei-Header
102
119
  def write_keywords
103
120
  photo = MiniExiftool.new @filename
104
- s = ''
105
- tags.each {|tag|
106
- if s == ''
107
- s = s + tag
108
- else
109
- # s = s + ' ' + tag
110
- # lieber durch Komma trennen, da sonst das Formular z.B. bei fotolia nicht
111
- # richtig gefüllt wird
112
- s = s + ',' + tag
113
- end
114
- }
115
- photo['keywords'] = s
121
+ #s = ''
122
+ #tags.each {|tag|
123
+ #if s == ''
124
+ #s = s + tag
125
+ #else
126
+ ## s = s + ' ' + tag
127
+ ## lieber durch Komma trennen, da sonst das Formular z.B. bei fotolia nicht
128
+ ## richtig gefüllt wird
129
+ #s = s + ',' + tag
130
+ #end
131
+ #}
132
+ # lieber durch Komma trennen, da sonst das Formular z.B. bei fotolia nicht
133
+ # richtig gefüllt wird
134
+ photo['keywords'] = tags.join(',')
116
135
 
117
136
  #TODO:
118
137
  photo['title'] = 'Testtitel'
@@ -120,4 +139,51 @@ class Photo
120
139
  photo.save
121
140
  end
122
141
 
142
+ private
143
+
144
+ # erstellt Key-Array aus String (getrennt durch newline,',' oder ' ')
145
+ def self.to_tags(s)
146
+ keys = []
147
+ s.split(/[, |\t\n]+/).each {|w|
148
+ keys << w
149
+ }
150
+ keys
151
+ end
152
+
153
+
154
+ # uebersetzt Tags von 'from_lang' nach 'to_lang'
155
+ # bisher realisiert: :de -> :en
156
+ # Achtung: bisher nur experimentell. Nicht verwenden!
157
+ def translate_tags(tags, from_lang, to_lang)
158
+ new_tags = []
159
+ tags.each {|tag|
160
+ new_tags << translate_tag(tag,from_lang,to_lang)
161
+ }
162
+ return new_tags
163
+ end
164
+
165
+ # uebersetzt ein Tag von 'from_lang' nach 'to_lang'
166
+ # bisher realisiert: :de -> :en
167
+ # Achtung: bisher nur experimentell. Nicht verwenden!
168
+ def translate_tag(tag,from_lang,to_lang)
169
+ if from_lang == :de and to_lang == :en
170
+ case tag
171
+ when 'Baum'
172
+ 'tree'
173
+ when 'Berlin'
174
+ 'Berlin'
175
+ when 'gwb'
176
+ 'gwb'
177
+ when 'Blume'
178
+ 'flower'
179
+ when 'Biene'
180
+ 'bee'
181
+ else
182
+ raise "unknown translation for '#{tag}' (#{from_lang} #{to_lang})"
183
+ end
184
+ else
185
+ raise "unknown translation codes: #{from_lang} #{to_lang}"
186
+ end
187
+ end
188
+
123
189
  end
@@ -28,7 +28,7 @@ class Sender
28
28
  end
29
29
 
30
30
  # wurde dieses File schon an diese Site gesendet?
31
- def sent_to?(p_filename,p_site)
31
+ def sent_to?(p_filename,p_site) #TODO:
32
32
  sendeliste = 'sendeliste.dat' #TODO:
33
33
  result = false
34
34
  File.open(sendeliste) {|f|
@@ -0,0 +1,121 @@
1
+ require 'net/ftp'
2
+
3
+ class Bigstockphoto < GenericSite
4
+
5
+ @@errors = 0
6
+
7
+ attr_reader :total_per_day
8
+ attr_accessor :ftp_user, :ftp_password, :max_errors
9
+
10
+ FTP_HOST = 'bigstockphoto.com'
11
+ SITENAME = 'bigstockphoto'
12
+
13
+ def initialize(name)
14
+ super
15
+ @config = load_config
16
+ begin
17
+ @user = @config[:bigstockphoto][:user]
18
+ @password = @config[:bigstockphoto][:password]
19
+ @ftp_user = @config[:bigstockphoto][:ftp_user]
20
+ @ftp_password = @config[:bigstockphoto][:ftp_password]
21
+ @total_per_day = @config[:bigstockphoto][:total_per_day]
22
+ rescue
23
+ puts 'cannot read configuration entries of '+SITENAME
24
+ end
25
+ end
26
+
27
+ def transfer(photo, dont_send=nil, dont_log=nil)
28
+ # falls nicht in config Datei eingetragen dann nicht senden
29
+ if @user == nil or @password == nil
30
+ return
31
+ end
32
+ unless photo.class == Photo
33
+ raise 'not a Photo object'
34
+ end
35
+ unless File.exist?(photo.filename)
36
+ raise "file #{photo.filename} does not exist"
37
+ end
38
+ if @ftp_user == nil or @ftp_password == nil
39
+ raise "ftp_user/ftp_password not set"
40
+ end
41
+ print "#{SITENAME}:#{photo.filename} ... "
42
+ $stdout.flush
43
+ if already_sent_site?(photo,SITENAME)
44
+ puts "already sent"
45
+ return :duplicate
46
+ end
47
+ if photo.portrait? and ! Bigstockphoto.can_handle_orientation?
48
+ photo.drehen
49
+ end
50
+ if site_can_handle_keywords?
51
+ photo.set_keywords(:de,:en) # Keywords uebersetzen
52
+ end
53
+ if ! dont_send
54
+ begin
55
+ ftp = Net::FTP.new(FTP_HOST)
56
+ ftp.login(@ftp_user,@ftp_password)
57
+ files=ftp.list('*')
58
+ if photo.portrait?
59
+ #p photo.rotated_filename
60
+ res = ftp.putbinaryfile(photo.rotated_filename)
61
+ else
62
+ res = ftp.putbinaryfile(photo.filename)
63
+ end
64
+ files =ftp.list('*')
65
+ #p files
66
+ ftp.close
67
+ rescue Errno::EPIPE
68
+ raise UploadException
69
+ end
70
+ end
71
+ @@errors = 0
72
+ puts 'OK'
73
+ if ! dont_log
74
+ File.open(SENDLIST,'a') {|f|
75
+ f.puts "#{SITENAME}\t#{photo.filename}\t#{Time.now}"
76
+ }
77
+ end
78
+ true
79
+ end
80
+
81
+ # Anzahl heute schon gesendeter Photos
82
+ def sent_today
83
+ site = SITENAME
84
+ return sent_today_site(site)
85
+ end
86
+
87
+ # wurde dieses Photo schon gesendet?
88
+ def already_sent?(photo)
89
+ already_sent_site?(photo,SITENAME)
90
+ end
91
+
92
+ # can site handle orientation?
93
+ def self.can_handle_orientation?
94
+ false #TODO: pruefen!
95
+ end
96
+
97
+ # Site akzeptiert diese Extension
98
+ def self.accept?(ext)
99
+ ext = ext.downcase
100
+ if ext[0,1] == '.'
101
+ ext = ext[1..-1]
102
+ end
103
+ case ext
104
+ when 'jpg','jpeg','eps','ai','psd','png','pdf'
105
+ return true
106
+ else
107
+ return false
108
+ end
109
+ end
110
+
111
+ def accept?(ext)
112
+ Bigstockphoto.accept?(ext)
113
+ end
114
+
115
+ private
116
+
117
+ def site_can_handle_keywords?
118
+ true #TODO: pruefen!
119
+ end
120
+
121
+ end # class