module WikiFlatFileStore CONTENT_EXTENSION = '.textile' REVISIONS_EXTENSION = '.yaml' DEFAULT_AUTHOR = 'the import script' def load_all_pages move_files_if_names_are_not_url_encoded pages_on_disk = Dir[ File.join( @folder, "*#{CONTENT_EXTENSION}" ) ].map { |filename| page_name_for( filename )} pages_in_memory = @pages.values.map { |page| page && page.name } ( pages_in_memory.compact | pages_on_disk ).each do |pagename| if check_disk_for_updated_page( pagename, true ) == :file_does_not_exist revise( pagename, $MESSAGES[:page_deleted], DEFAULT_AUTHOR ) end end end def check_disk_for_updated_page( pagename, force = false ) return unless force || $SETTINGS[:check_files_every] # We don't care about file changes filename = filename_for_content( pagename ) return :file_does_not_exist unless File.exists?( filename ) # File doesn't exist on disk return load_page( filename ) unless page_named( pagename )# File is new on the disk, but not yet in memory return load_page( filename ) if content_newer_than_revisions?( page_named(pagename) ) # File is newer on disk return nil end def load_page( filename ) mutate( page_name_for( filename ) ) do |page| disk_content = load_content( page ) return nil if disk_content == page.content # No change, disk is the same as memory # We now know that the content on disk is different from that in memory page.revisions = load_revisions( page ) if page.revisions.empty? # Load revisions from disk if none known # assumes disk revisions are ALWAYS up to date with memory? # We now know what the page content and the page revisions should be. But not if the revisions are up to date if content_newer_than_revisions?( page ) # The textile file has been modified, but the array file has not been updated to match page.content = reconstruct_content_from_revisions( page.revisions ) page.revise( disk_content, DEFAULT_AUTHOR ) save_revisions( page ) else # The textile file and the array file are in sync. page.content = disk_content end add_page_to_index( page ) [ page.revisions.last, :dont_save ] end end def load_content( page ) IO.readlines( filename_for_content( page.name ) ).join end def load_revisions( page ) return [] unless File.exists?( filename_for_revisions( page.name ) ) revisions = [] begin File.open( filename_for_revisions( page.name ) ) { |file| YAML.each_document( file ) { |array| next unless array.is_a? Array next unless array.size == 4 next unless array[0].is_a? Integer revisions[ array[0] ] = Revision.new( page, *array ) } } rescue $LOG.error "Error loading revisions with #{$!.to_s} in file #{page.name}" end revisions.each_with_index { |r,i| $LOG.error "#{page.name} missing revision #{i}" unless r } revisions end def content_newer_than_revisions?( page ) return true if page.empty? File.ctime(filename_for_content( page.name )) > File.ctime(filename_for_revisions(page.name)) end def reconstruct_content_from_revisions( revisions ) content = [] revisions.each { |revision| content = Diff::LCS.patch( content, revision.changes, :patch ) } content.join("\n") end def move_files_if_names_are_not_url_encoded Dir[ File.join( @folder, "*#{CONTENT_EXTENSION}" ) ].each do |filename| basename = File.basename( filename, '.*') next if basename.url_decode.to_valid_pagename.url_encode == basename # All ok, so no worry new_name = File.join( File.dirname(filename), File.unique_filename( File.dirname(filename), basename.url_decode.to_valid_pagename.url_encode + File.extname( filename) ) ) File.rename(filename, new_name ) end end def save( page ) add_page_to_index( page ) save_content( page ) save_revisions( page ) end def save_content( page ) File.open(filename_for_content( page.name ), 'w' ) { |file| file.puts page.content } end # Appends the last revision onto the yaml file def save_revisions( page ) $LOG.info "Saving revisions for #{page.name}" File.open(filename_for_revisions( page.name ), 'a' ) do |file| YAML.dump( page.revisions.last.to_a, file ) file.puts # Needed to ensure that documents are separated end end def page_name_for( filename ) File.basename( filename, '.*').url_decode end def filename_for_content( pagename ) File.join( @folder, "#{pagename.url_encode}#{CONTENT_EXTENSION}" ) end def filename_for_revisions( pagename ) File.join( @folder, "#{pagename.url_encode}#{REVISIONS_EXTENSION}" ) end end