fix cookie
[booh] / bin / booh
1 #!/usr/bin/ruby
2 #
3 #                         *  BOOH  *
4 #
5 # A.k.a `Best web-album Of the world, Or your money back, Humerus'.
6 #
7 # The acronyn sucks, however this is a tribute to Dragon Ball by
8 # Akira Toriyama, where the last enemy beaten by heroes of Dragon
9 # Ball is named "Boo". But there was already a free software project
10 # called Boo, so this one will be it "Booh". Or whatever.
11 #
12 #
13 # Copyright (c) 2004 Guillaume Cottenceau <gc3 at bluewin.ch>
14 #
15 # This software may be freely redistributed under the terms of the GNU
16 # public license version 2.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21
22 require 'getoptlong'
23 require 'gettext'
24 include GetText
25 require 'rexml/document'
26 include REXML
27
28 require 'booh/booh-lib'
29 require 'booh/html-merges'
30
31 #- bind text domain as soon as possible because some _() functions are called early to build data structures
32 bindtextdomain("booh")
33
34 #- options
35 $options = [
36     [ '--help',          '-h', GetoptLong::NO_ARGUMENT,       _("Get help message") ],
37
38     [ '--no-check',      '-n', GetoptLong::NO_ARGUMENT,       _("Don't check for needed external programs at startup") ],
39
40     [ '--source',        '-s', GetoptLong::REQUIRED_ARGUMENT, _("Directory which contains original images/videos as files or subdirs") ],
41     [ '--destination',   '-d', GetoptLong::REQUIRED_ARGUMENT, _("Directory which will contain the web-album") ],
42 #    [ '--clean',         '-c', GetoptLong::NO_ARGUMENT,       _("Clean destination directory") ],
43
44     [ '--theme',         '-t', GetoptLong::REQUIRED_ARGUMENT, _("Select HTML theme to use") ],
45     [ '--config',        '-C', GetoptLong::REQUIRED_ARGUMENT, _("File containing config listing images and videos within directories with captions") ],
46     [ '--config-skel',   '-k', GetoptLong::REQUIRED_ARGUMENT, _("Filename where the script will output a config skeleton") ],
47     [ '--merge-config',  '-M', GetoptLong::REQUIRED_ARGUMENT, _("File containing config listing, where to merge new images/videos from --source") ],
48
49     [ '--mproc',         '-m', GetoptLong::REQUIRED_ARGUMENT, _("Specify the number of processors for multi-processors machines") ],
50
51     [ '--for-gui',       '-g', GetoptLong::NO_ARGUMENT,       _("Do the minimum work to be able to see the album under the GUI (don't generate all thumbnails)") ],
52
53     [ '--verbose-level', '-v', GetoptLong::REQUIRED_ARGUMENT, _("Set max verbosity level (0: errors, 1: warnings, 2: important messages, 3: other messages)") ],
54 ]
55
56 #- default values for some globals 
57 $switches = []
58 $stdout.sync = true
59
60 def usage
61     puts _("Usage: %s [OPTION]...") % File.basename($0)
62     $options.each { |ary|
63         printf " %3s, %-15s %s\n", ary[1], ary[0], ary[3]
64     }
65 end
66
67 def handle_options
68     parser = GetoptLong.new
69     parser.set_options(*$options.collect { |ary| ary[0..2] })
70     begin
71         parser.each_option do |name, arg|
72             case name
73             when '--help'
74                 usage
75                 exit(0)
76
77             when '--no-check'
78                 $no_check = true
79
80             when '--source'
81                 $source = arg.sub(%r|/$|, '')
82                 if !File.directory?($source)
83                     die _("Argument to --source must be a directory")
84                 end
85             when '--destination'
86                 $dest = arg.sub(%r|/$|, '')
87                 if File.exists?($dest) && !File.directory?($dest)
88                     die _("If --destination exists, it must be a directory")
89                 end
90                 if $dest != make_dest_filename($dest)
91                     die _("Sorry, destination directory can't contain non simple alphanumeric characters.")
92                 end
93 #            when '--clean'
94 #                system("rm -rf #{$dest}")
95
96             when '--theme'
97                 $theme = arg
98             when '--config'
99                 if File.readable?(arg)
100                     $xmldoc = REXML::Document.new File.new(arg)
101                     $mode = 'use_config'
102                 else
103                     die _('Config file does not exist or is unreadable.')
104                 end
105             when '--config-skel'
106                 if File.exists?(arg)
107                     msg 1, _("Config skeleton file already exists, backuping to %s.backup") % arg
108                     File.rename(arg, "#{arg}.backup")
109                 end
110                 $config_writeto = arg
111                 $mode = 'gen_config'
112             when '--merge-config'
113                 if File.readable?(arg)
114                     msg 2, _("Merge config notice: backuping current config file to %s.backup") % arg
115                     $xmldoc = REXML::Document.new File.new(arg)
116                     File.rename(arg, "#{arg}.backup")
117                     $config_writeto = arg
118                     $mode = 'merge_config'
119                 else
120                     die _('Config file does not exist or is unreadable.')
121                 end
122
123             when '--mproc'
124                 $mproc = arg.to_i
125                 $pids = []
126
127             when '--for-gui'
128                 $forgui = true
129
130             when '--verbose-level'
131                 $verbose_level = arg.to_i
132
133             end
134         end
135     rescue
136         puts $!
137         usage
138         exit(1)
139     end
140
141     if !$source && $xmldoc
142         $source = $xmldoc.root.attributes['source']
143         $dest = $xmldoc.root.attributes['destination']
144         $theme = $xmldoc.root.attributes['theme']
145     end
146
147     if !$source
148         die _("Missing --source or --config parameter.")
149     end
150     if !$dest
151         die _("Missing --destination parameter.")
152     end
153     if !$theme
154         $theme = 'simple'
155     end
156     select_theme($theme)
157
158     if !$xmldoc
159         $xmldoc = Document.new "<booh version='#{$VERSION}' source='#{utf8($source)}' destination='#{utf8($dest)}' theme='#{$theme}'/>"
160         $xmldoc << XMLDecl.new( XMLDecl::DEFAULT_VERSION, $CURRENT_CHARSET )
161         $mode = 'gen_config'
162     end
163
164 end
165
166 def read_config
167     $config = {}
168 end
169
170 def write_config
171 end
172
173 def check_installation
174     if $no_check
175         return
176     end
177     missing = %w(convert identify exif transcode mencoder).delete_if { |prg| system("which #{prg} >/dev/null 2>/dev/null") }
178     if missing != []
179         die _("The following programs are typically needed: `%s'. Re-run with --no-check if you're sure you're fine without them.") % missing.join(', ')
180     end
181 end
182
183 def replace_line(surround, keyword, line)
184     begin
185         contents = eval "$#{keyword}"
186         line.sub!(/#{surround}#{keyword}#{surround}/, contents)
187     rescue TypeError
188         die _("No `%s' found for substitution") % keyword
189     end
190 end
191
192 def build_html_skeletons
193     $html_images     = File.open("#{$FPATH}/themes/#{$theme}/skeleton_image.html").readlines
194     $html_thumbnails = File.open("#{$FPATH}/themes/#{$theme}/skeleton_thumbnails.html").readlines
195     $html_index      = File.open("#{$FPATH}/themes/#{$theme}/skeleton_index.html").readlines
196     for line in $html_images + $html_thumbnails + $html_index
197         while line =~ /~~~(\w+)~~~/
198             replace_line('~~~', $1, line)
199         end
200     end
201 end
202
203 def find_caption_value(xmldir, filename)
204     xmldir.elements["[@filename='#{utf8(filename)}']"].attributes['caption']
205 end
206
207 def find_captions(xmldir, images)
208     return images.collect { |img| find_caption_value(xmldir, img) }
209 end
210
211 #- stolen from CVSspam
212 def urlencode(text)
213   text.sub(/[^a-zA-Z0-9\-,.*_\/]/) do
214     "%#{sprintf('%2X', $&[0])}"
215   end
216 end
217
218 def html_reload_to_thumbnails
219     html_reload_to_thumbnails = $preferred_size_reloader.clone
220     html_reload_to_thumbnails.gsub!(/~~theme~~/, $theme)
221     html_reload_to_thumbnails.gsub!(/~~default_size~~/, $default_size['name'])
222     return html_reload_to_thumbnails
223 end
224
225 def discover_iterations(iterations, line)
226     if line =~ /~~iterate(\d)_open(_max(\d+))?~~/
227         for iter in iterations.values
228             if iter['open']
229                 iter['open'] = false
230                 iter['close_wait'] = $1.to_i
231             end
232         end
233         iterations[$1.to_i] = { 'open' => true, 'max' => $3, 'opening' => '', 'closing' => '' }
234         if $1.to_i == 1
235             line.sub!(/.*/, '~~thumbnails~~')
236         else
237             line.sub!(/.*/, '')
238         end
239     elsif line =~ /~~iterate(\d)_close~~/
240         iterations[$1.to_i]['open']  = false;
241         iterations[$1.to_i]['close'] = true;
242         line.sub!(/.*/, '')
243     else
244         for iter in iterations.values
245             if iter['open']
246                 iter['opening'] += line
247                 line.sub!(/.*/, '')
248             end
249             if !iter['close'] && iter['close_wait'] && iterations[iter['close_wait']]['close']
250                 iter['closing'] += line
251                 line.sub!(/.*/, '')
252             end
253         end
254     end
255 end
256
257 def reset_iterations(iterations)
258     for iter in iterations.values
259         iter['value'] = 1
260     end
261 end
262
263 def run_iterations(iterations)
264     html = ''
265     for level in iterations.keys.sort
266         if iterations[level]['value'] == 1 || level == iterations.keys.max
267             html += iterations[level]['opening']
268         end
269         iterations[level]['value'] += 1
270         if iterations[level]['max'] && iterations[level]['value'] > iterations[level]['max'].to_i
271             iterations[level]['value'] = 1
272             iterations[level-1]['value'] = 1
273             html += iterations[level-1]['closing']
274         end
275     end
276     return html
277 end
278
279 def close_iterations(iterations)
280     html = ''
281     for level in iterations.keys.sort.reverse
282         html += iterations[level]['closing']
283     end
284     return html
285 end
286
287 def img_element(fullpath)
288     if size = get_image_size(fullpath)
289         sizespec = 'width="${size[:x]}" height="${size[:y]}"'
290     else
291         sizespec = ''
292     end
293     return '<img src="' + File.basename(fullpath) + '" ' + sizespec + ' border="0"/>'
294 end
295
296 def walk_source_dir
297
298     `find #{$source} -type d`.sort.each { |dir|
299         dir.chomp!
300         if dir =~ /'/
301             die _("Source directory or sub-directories can't contain a single-quote character, sorry.")
302         end
303
304         #- place xml document on proper node if exists, else create
305         xmldir = $xmldoc.elements["//dir[@path='#{utf8(dir)}']"]
306         if $mode == 'use_config'
307             if !xmldir
308                 next
309             end
310         else
311             if $mode == 'gen_config' || ($mode == 'merge_config' && !xmldir)
312                 #- add the <dir..> element if necessary
313                 parent = File.dirname(dir)
314                 xmldir = $xmldoc.elements["//dir[@path='#{utf8(parent)}']"]
315                 if !xmldir
316                     xmldir = $xmldoc.root
317                 end
318                 xmldir = xmldir.add_element 'dir', { 'path' => utf8(dir) }
319             end
320         end
321
322         #- read images/videos entries from config or from directories depending on mode
323         entries = []
324         if $mode == 'use_config'
325             msg 2, _("Handling %s from config list...") % dir
326             xmldir.elements.each { |element|
327                 if %w(image video).include?(element.name)
328                     entries << from_utf8(element.attributes['filename'])
329                 end
330             }
331         else
332             msg 2, _("Examining %s...") % dir
333             entries = Dir.entries(dir).sort
334             #- populate config in case of gen_config, add new files in case of merge_config
335             for file in entries
336                 type = entry2type(file)
337                 if type && !xmldir.elements["#{type}[@filename='#{utf8(file)}']"]
338                     xmldir.add_element type, { "filename" => utf8(file), "caption" => utf8(file.sub(/\.[^\.]+$/, '')[0..17]) }
339                 end
340             end
341             if $mode == 'merge_config'
342                 #- cleanup removed files from config and reread entries from config to get proper ordering
343                 entries = []
344                 xmldir.elements.each { |element|
345                     fullpath = "#{dir}/#{from_utf8(element.attributes['filename'])}"
346                     if %w(image video).include?(element.name)
347                         if !File.readable?(fullpath)
348                             msg 1, _("Config merge: removing %s from config; use the backup file to retrieve caption info if this was a mistake") % fullpath
349                             xmldir.delete(element)
350                         else
351                             entries << from_utf8(element.attributes['filename'])
352                         end
353                     end
354                 }
355             end
356         end
357         images = entries.find_all { |e| entry2type(e) == 'image' }
358         msg 3, _("\t%s images") % images.length
359         videos = entries.find_all { |e| entry2type(e) == 'video' }
360         msg 3, _("\t%s videos") % videos.length
361
362         dest_dir = make_dest_filename(dir.sub(/^#{Regexp.quote($source)}/, $dest))
363         system("mkdir -p '#{dest_dir}'")
364
365         #- pass through if there are no images and videos
366         if images.size == 0 && videos.size == 0
367             next
368         end
369
370         msg 2, _("Outputting in %s...") % dest_dir
371
372         #- populate data structure with sizes from theme
373         for sizeobj in $images_size
374             fullscreen_images ||= {}
375             fullscreen_images[sizeobj['name']] = []
376             thumbnail_images ||= {}
377             thumbnail_images[sizeobj['name']] = []
378             thumbnail_videos ||= {}
379             thumbnail_videos[sizeobj['name']] = []
380         end
381
382         images.size >= 1 and msg 3, _("\tcreating images thumbnails...")
383
384         #- create thumbnails for images
385         images.each { |img|
386             base_dest_img = dest_dir + '/' + make_dest_filename(img.sub(/\.[^\.]+$/, ''))
387             if $forgui
388                 thumbnail_dest_img = base_dest_img + "-#{$default_size['thumbnails']}.jpg"
389                 gen_thumbnails_element("#{dir}/#{img}", xmldir, true, [ { 'filename' => thumbnail_dest_img, 'size' => $default_size['thumbnails'] } ])
390             else
391                 for sizeobj in $images_size
392                     size_fullscreen = sizeobj['fullscreen']
393                     size_thumbnails = sizeobj['thumbnails']
394                     fullscreen_dest_img = base_dest_img + "-#{size_fullscreen}.jpg"
395                     thumbnail_dest_img  = base_dest_img + "-#{size_thumbnails}.jpg"
396                     fullscreen_images[sizeobj['name']] << File.basename(fullscreen_dest_img)
397                     thumbnail_images[sizeobj['name']]  << File.basename(thumbnail_dest_img)
398                     gen_thumbnails_element("#{dir}/#{img}", xmldir, true, [ { 'filename' => fullscreen_dest_img, 'size' => size_fullscreen },
399                                                                             { 'filename' => thumbnail_dest_img, 'size' => size_thumbnails } ])
400                 end
401             end
402         }
403
404         videos.size >= 1 and msg 3, _("\tcreating videos thumbnails...")
405
406         #- create thumbnails for videos
407         videos.each { |video|
408             thumbnail_ok = true
409             if $forgui
410                 thumbnail_dest_img = dest_dir + '/' + make_dest_filename(video.sub(/\.[^\.]+$/, '')) + "-#{$default_size['thumbnails']}.jpg"
411                 thumbnail_ok &&= gen_thumbnails_element("#{dir}/#{video}", xmldir, true, [ { 'filename' => thumbnail_dest_img, 'size' => $default_size['thumbnails'] } ])
412             else
413                 for sizeobj in $images_size
414                     size_thumbnails = sizeobj['thumbnails']
415                     thumbnail_dest_img = dest_dir + '/' + make_dest_filename(video.sub(/\.[^\.]+$/, '')) + "-#{size_thumbnails}.jpg"
416                     thumbnail_videos[sizeobj['name']] << File.basename(thumbnail_dest_img)
417                     thumbnail_ok &&= gen_thumbnails_element("#{dir}/#{video}", xmldir, true, [ { 'filename' => thumbnail_dest_img, 'size' => size_thumbnails } ])
418                 end
419             end
420             destvideo = "#{dest_dir}/#{video}"
421             if !File.exists?(destvideo)
422                 psys("cp '#{dir}/#{video}' '#{destvideo}'")
423             end
424             #- cleanup temp
425             system("rm -f #{dest_dir}/screenshot.jpg000000.jpg")
426         }
427
428         if !$forgui
429             #- copy any resource file that goes with the theme (css, images..)
430             for entry in Dir.entries("#{$FPATH}/themes/#{$theme}")
431                 if !%w(. .. skeleton_image.html skeleton_thumbnails.html skeleton_index.html metadata CVS).include?(entry)
432                     if !File.exists?("#{dest_dir}/#{entry}")
433                         psys("cp '#{$FPATH}/themes/#{$theme}/#{entry}' '#{dest_dir}'")
434                     end
435                 end
436             end
437
438             #- fake for gettext to find these; if themes need more sizes, english name for them should be added here
439             sizenames = { 'small' => utf8(_("small")), 'medium' => utf8(_("medium")), 'large' => utf8(_("large")) }
440
441             msg 3, _("\tgenerating HTML pages...")
442             
443             #- generate thumbnails.html (page with thumbnails)
444             for sizeobj in $images_size
445                 html = $html_thumbnails.collect { |l| l.clone }
446                 iterations = {}
447                 for i in html
448                     i.sub!(/~~run_slideshow~~/, images.size <= 1 ? '' : '<a href="image-' + sizeobj['name'] + '.html?run_slideshow">' + utf8(_('Run slideshow!')) + '</a>')
449                     i.sub!(/~~title~~/, xmldir.attributes['thumbnails-caption'] || utf8(File.basename(dir)))
450                     for sizeobj2 in $images_size
451                         if sizeobj != sizeobj2
452                             i.sub!(/~~size_#{sizeobj2['name']}~~/, '<a href="thumbnails-' + sizeobj2['name'] + '.html">' + sizenames[sizeobj2['name']] + '</a>')
453                         else
454                             i.sub!(/~~size_#{sizeobj2['name']}~~/, sizenames[sizeobj2['name']])
455                         end
456                     end
457                     discover_iterations(iterations, i)
458                 end
459                 html_thumbnails = ''
460                 reset_iterations(iterations)
461                 for file in entries
462                     type = images.include?(file) ? 'image' : videos.include?(file) ? 'video' : nil
463                     if type
464                         html_thumbnails += run_iterations(iterations)
465                         if type == 'image'
466                             index = images.index(file)
467                             html_thumbnails.gsub!(/~~image_iteration~~/,
468                                                   '<a href="image-' + sizeobj['name'] + '.html?current=' + fullscreen_images[sizeobj['name']][index] + '">' +
469                                                       img_element("#{dest_dir}/#{thumbnail_images[sizeobj['name']][index]}") + '</a>')
470                             html_thumbnails.gsub!(/~~caption_iteration~~/,
471                                                   find_caption_value(xmldir, images[index]) || utf8(images[index]))
472                             html_thumbnails.gsub!(/~~ifimage\?~~(.+?)~~fi~~/) { $1 }
473                             html_thumbnails.gsub!(/~~ifvideo\?~~(.+?)~~fi~~/, '')
474                         elsif type == 'video'
475                             index = videos.index(file)
476                             if File.exists?("#{dest_dir}/#{thumbnail_videos[sizeobj['name']][index]}")
477                                 html_thumbnails.gsub!(/~~image_iteration~~/,
478                                                       '<a href="' + videos[index] + '">' + img_element("#{dest_dir}/#{thumbnail_videos[sizeobj['name']][index]}") + '</a>')
479                             else
480                                 html_thumbnails.gsub!(/~~image_iteration~~/,
481                                                       '<a href="' + videos[index] + '">' + utf8(_("(no preview)")) + '</a>')
482                             end
483                             html_thumbnails.gsub!(/~~caption_iteration~~/,
484                                                   find_caption_value(xmldir, videos[index]) || utf8(videos[index]))
485                             html_thumbnails.gsub!(/~~ifimage\?~~(.+?)~~fi~~/, '')
486                             html_thumbnails.gsub!(/~~ifvideo\?~~(.+?)~~fi~~/) { $1 }
487                         end
488                     end
489                 end
490                 html_thumbnails += close_iterations(iterations)
491                 for i in html
492                     i.sub!(/~~thumbnails~~/, html_thumbnails)
493                     i.gsub!(/~~theme~~/, $theme)
494                     i.gsub!(/~~current_size~~/, sizeobj['name'])
495                 end
496                 ios = File.open("#{dest_dir}/thumbnails-#{sizeobj['name']}.html", "w")
497                 ios.write(html)
498                 ios.close
499             end
500
501             #- generate "main" thumbnails.html page that will reload to correct size thanks to cookie
502             ios = File.open("#{dest_dir}/thumbnails.html", "w")
503             ios.write(html_reload_to_thumbnails)
504             ios.close
505
506             #- generate image.html (page with fullscreen images)
507             if images.size > 0
508                 #- don't ask me why I need so many backslashes... the aim is to print \\\" for each " in the javascript source
509                 captions4js = find_captions(xmldir, images).collect { |e| e ? '"' + e.gsub('"', '\\\\\\\\\\\\\\\\\"' ) + '"' : '""' }.join(', ')
510                 for sizeobj in $images_size
511                     html = $html_images.collect { |l| l.clone }
512                     images4js = fullscreen_images[sizeobj['name']].collect { |e| "\"#{e}\"" }.join(', ')
513                     otherimages4js = ''
514                     othersizes = []
515                     for sizeobj2 in $images_size
516                         if sizeobj != sizeobj2
517                             otherimages4js += "var images_#{sizeobj2['name']} = new Array(" + fullscreen_images[sizeobj2['name']].collect { |e| "\"#{e}\"" }.join(', ') + ")\n"
518                             othersizes << "\"#{sizeobj2['name']}\""
519                         end
520                     end
521                     for i in html
522                         i.sub!(/~~images~~/, images4js)
523                         i.sub!(/~~other_images~~/, otherimages4js)
524                         i.sub!(/~~other_sizes~~/, othersizes.join(', '))
525                         i.sub!(/~~captions~~/, captions4js)
526                         i.sub!(/~~title~~/, xmldir.attributes['thumbnails-caption'] || utf8(File.basename(dir)))
527                         i.sub!(/~~thumbnails~~/, '<a href="thumbnails-' + sizeobj['name'] + '.html">' + utf8(_('Return to thumbnails')) + '</a>')
528                         i.sub!(/~~theme~~/, $theme)
529                         i.sub!(/~~current_size~~/, sizeobj['name'])
530                         for sizeobj2 in $images_size
531                             if sizeobj != sizeobj2
532                                 i.sub!(/~~size_#{sizeobj2['name']}~~/,
533                                        '<a href="image-' + sizeobj2['name'] + '.html" id="link' + sizeobj2['name'] + '">' + sizenames[sizeobj2['name']] + '</a>')
534                             else
535                                 i.sub!(/~~size_#{sizeobj2['name']}~~/,
536                                        sizenames[sizeobj2['name']])
537                             end
538                         end
539                     end
540                     ios = File.open("#{dest_dir}/image-#{sizeobj['name']}.html", "w")
541                     ios.write(html)
542                     ios.close
543                 end
544             end
545         end
546     }
547
548     msg 3, ''
549
550     #- add attributes to <dir..> elements needing so
551     if $mode != 'use_config'
552         msg 3, _("\tfixating configuration file...")
553         $xmldoc.elements.each('//dir') { |element|
554             path = captionpath = element.attributes['path']
555             child = element
556             captionfile = nil
557             while true
558                 child = child.elements[1]
559                 if !child
560                     element.remove  #- means we have a directory with nothing interesting in it
561                     break
562                 elsif child.name == 'dir'
563                     captionpath = child.attributes['path']
564                 else
565                     captionfile = "#{captionpath}/#{child.attributes['filename']}"
566                     break
567                 end
568             end
569             basename = File.basename(path)
570             if element.elements['dir']
571                 if !element.attributes['subdirs-caption']
572                     element.add_attribute('subdirs-caption', basename)
573                 end
574                 if !element.attributes['subdirs-captionfile']
575                     element.add_attribute('subdirs-captionfile', captionfile)
576                 end
577             end
578             if element.elements['image'] || element.elements['video']
579                 if !element.attributes['thumbnails-caption']
580                     element.add_attribute('thumbnails-caption', basename)
581                 end
582                 if !element.attributes['thumbnails-captionfile']
583                     element.add_attribute('thumbnails-captionfile', captionfile)
584                 end
585             end
586         }
587     end
588
589     #- write down to disk config if necessary
590     if $config_writeto
591         ios = File.open($config_writeto, "w")
592         $xmldoc.write(ios, 0)
593         ios.close
594     end
595
596     if $forgui
597         msg 3, _(" completed necessary stuff for GUI, exiting.")
598         return
599     end
600
601     #- second pass to create index.html files
602     msg 3, _("\trescanning directories to generate all `index.html' files...")
603
604     `find #{$source} -type d`.each { |dir|
605         dir.chomp!
606         xmldir = $xmldoc.elements["//dir[@path='#{utf8(dir)}']"]
607         if !xmldir
608             next
609         end
610         dest_dir = make_dest_filename(dir.sub(/^#{Regexp.quote($source)}/, $dest))
611
612         if xmldir.elements['dir']
613             html = $html_index.collect { |l| l.clone }
614             iterations = {}
615             for i in html
616                 caption = xmldir.attributes['subdirs-caption']
617                 i.gsub!(/~~title~~/, caption)
618                 if xmldir.parent.name == 'dir'
619                     nav = ''
620                     path = '..'
621                     parent = xmldir.parent
622                     while parent.name == 'dir'
623                         parentcaption = parent.attributes['subdirs-caption']
624                         nav = "<a href='#{path}/index.html'>#{parentcaption}</a> #{utf8(_(" > "))} #{nav}"
625                         path += '/..'
626                         parent = parent.parent
627                     end
628                     i.gsub!(/~~ifnavigation\?~~(.+?)~~fi~~/) { $1 }
629                     i.gsub!(/~~navigation~~/, nav + caption)
630                 else
631                     i.gsub!(/~~ifnavigation\?~~(.+?)~~fi~~/, '')
632                 end
633                 discover_iterations(iterations, i)
634             end
635             
636             html_index = ''
637             reset_iterations(iterations)
638             
639             #- deal with "current" album (directs to "thumbnails" page)
640             if xmldir.attributes['thumbnails-caption']
641                 thumbnail = "#{dest_dir}/thumbnails-thumbnail.jpg"
642                 gen_thumbnails_subdir(from_utf8(xmldir.attributes['thumbnails-captionfile']), xmldir, false,
643                                       [ { 'filename' => thumbnail, 'size' => $albums_thumbnail_size } ], 'thumbnails')
644                 html_index += run_iterations(iterations)
645                 html_index.gsub!(/~~image_iteration~~/, "<a href='thumbnails.html'>" + img_element(thumbnail) + '</a>')
646                 html_index.gsub!(/~~caption_iteration~~/, xmldir.attributes['thumbnails-caption'])
647             end
648             #- cleanup temp for videos
649             system("rm -f #{dest_dir}/screenshot.jpg000000.jpg")
650
651             #- deal with sub-albums (direct to subdirs/index.html pages)
652             xmldir.elements.each('dir') { |child|
653                 subdir = make_dest_filename(from_utf8(File.basename(child.attributes['path'])))
654                 thumbnail = "#{dest_dir}/thumbnails-#{subdir}.jpg"
655                 html_index += run_iterations(iterations)
656                 captionfile, caption = find_subalbum_caption_info(child)
657                 gen_thumbnails_subdir(captionfile, child, false,
658                                       [ { 'filename' => thumbnail, 'size' => $albums_thumbnail_size } ], find_subalbum_info_type(child))
659                 html_index.gsub!(/~~caption_iteration~~/, caption)
660                 html_index.gsub!(/~~image_iteration~~/, "<a href='#{make_dest_filename(subdir)}/index.html'>" + img_element(thumbnail) + '</a>')
661                 #- cleanup temp for videos
662                 system("rm -f #{dest_dir}/screenshot.jpg000000.jpg")
663             }
664
665             html_index += close_iterations(iterations)
666
667             for i in html
668                 i.gsub!(/~~thumbnails~~/, html_index)
669             end
670             
671         else
672             html = html_reload_to_thumbnails
673         end
674
675         ios = File.open("#{dest_dir}/index.html", "w")
676         ios.write(html)
677         ios.close
678
679         #- substitute "return to albums" correctly
680         `find '#{dest_dir}' -maxdepth 1 -name "thumbnails*.html"`.each { |thumbnails|
681             thumbnails.chomp!
682             contents = File.open(thumbnails.chomp).readlines
683             for i in contents
684                 if xmldir.elements['dir']
685                     i.sub!(/~~return_to_albums~~/, '<a href="index.html">' + utf8(_('Return to albums')) + '</a>')
686                 else
687                     if xmldir.parent.name == 'dir'
688                         i.sub!(/~~return_to_albums~~/, '<a href="../index.html">' + utf8(_('Return to albums')) + '</a>')
689                     else
690                         i.sub!(/~~return_to_albums~~/, '')
691                     end
692                 end
693             end
694             ios = File.open(thumbnails, "w")
695             ios.write(contents)
696             ios.close
697         }
698     }
699
700     msg 3, _(" all done.")
701 end
702
703 handle_options
704 read_config
705 check_installation
706
707 build_html_skeletons
708
709 walk_source_dir