lighttroupe/luz

View on GitHub
engine/project.rb

Summary

Maintainability
B
6 hrs
Test Coverage
multi_require 'callbacks', 'project_effect', 'zaml'

# For File.mv
require 'fileutils'    # ruby 1.9
#YAML::ENGINE.yamler = 'syck' if defined?(YAML::ENGINE) && YAML::ENGINE.respond_to?(:yamler)

require 'callbacks'

class Project < UserObject
    include Callbacks

    callback :changed

    FILE_VERSION = 1
    FILE_EXTENSION = 'luz'
    FILE_EXTENSION_WITH_DOT = '.luz'
    FILE_PATTERN = '*.' + FILE_EXTENSION

    OBJECT_SYMBOLS = [:actors, :directors, :themes, :curves, :variables, :events, :effects]
    OBJECT_SYMBOL_TO_CLASS = {:actors => Actor, :directors => Director, :themes => Theme, :curves => Curve, :variables => Variable, :events => Event, :effects => ProjectEffect}
    CLASS_TO_OBJECT_SYMBOL = {Actor => :actors, Director => :directors, Theme => :themes, Curve => :curves, Variable => :variables, Event => :events, ProjectEffect => :effects}

    # readers
    attr_reader *OBJECT_SYMBOLS

    # writers
    OBJECT_SYMBOLS.each { |name|
        define_method(name.to_s + '=') { |value|
            return if instance_variable_get('@' + name.to_s) == value
            instance_variable_set('@' + name.to_s, value)
            changed!
        }
    }

    attr_reader :path, :change_count, :missing_plugin_names, :effects

    def initialize
        @last_save_time = Time.now
        clear
        @missing_plugin_names = []
    end

    # (used by editor)
    def title
        'Project Plugins'
    end

    def clear
        # set all to []
        OBJECT_SYMBOLS.each { |obj_type| instance_variable_set("@#{obj_type}", []) }
        @change_count = 0
        @path = nil
    end

    def valid_child_class?(klass)
        klass.ancestors.include? ProjectEffect
    end

    #
    # Change monitoring
    #
    def changed?
        @change_count > 0
    end

    def changed!
        @change_count += 1
        changed_notify(@change_count)
    end

    def not_changed!
        if @change_count != 0
            @change_count = 0
            changed_notify(@change_count)
        end
    end

    def time_since_save
        Time.now - @last_save_time
    end

    def file_path
        if @path
            File.split(@path).first
        end
    end

    def media_file_path(absolute_path)
        raise ArgumentError.new("expected #{absolute_path} to exist") unless File.exists?(absolute_path)

        puts "[#{self.file_path}] media_file_path(#{absolute_path}) => "
        if absolute_path.has_prefix?(self.file_path)
            puts absolute_path
            absolute_path
        else
            destination = File.join(file_path, File.basename(absolute_path))
            FileUtils.cp(absolute_path, destination)
            puts destination
            destination
        end
    end

    #
    # Loading
    #
    def load_from_path(path)
        path = File.absolute_path(path)
        clear
        append_from_path(path)
        @path = path
        not_changed!        # A freshly loaded project should not be marked 'changed'
        true
    end

    def append_from_path(path)
        File.open(path, 'r') { |file|
            append_from_file(file)
        }
    end

    def append_from_data(data)
        append_from_file(data)
    end

    #
    # Saving
    #
    def save
        save_to_path(@path)
    end

    def save_to_path(path)
        if save_copy_to_path(path)
            @path = path
            @last_save_time = Time.now
            not_changed!
            true
        else
            false
        end
    end

    def save_copy_to_path(path)
        # save to a .tmp file first, and once that is known to work,
        tmp_path = path + '.tmp'
        File.open(tmp_path, 'w+') { |tmp_file|
            if save_to_file(tmp_file)
                File.rename(tmp_path, path)
                return true
            end
        }
        false
    end

    #
    # Iterating
    #
    def each_user_object_array
        OBJECT_SYMBOLS.each { |obj_type|
            yield instance_variable_get("@#{obj_type}")
        }
    end

    def each_user_object
        OBJECT_SYMBOLS.each { |obj_type|
            instance_variable_get("@#{obj_type}").each { |user_object|
                yield user_object
                if user_object.respond_to? :effects
                    user_object.effects.each { |effect|
                        yield effect
                    }
                end
            }
        }
    end

private

    def save_to_file(file)
        begin
            file << serialize
            file.flush
            return true
        rescue Exception => e
            $gui.negative_message e.message
            return false
        end

        # For comparison:
        #File.open('yamltest.luz', 'w+') { |file|
        #    YAML.dump(saved_objects, file)
        #}
    end

    def serialize
        data = ''
        saved_objects = {:version => FILE_VERSION}
        OBJECT_SYMBOLS.each { |obj_type| saved_objects[obj_type] = self.send("#{obj_type}") }
        ZAML.dump(saved_objects, data)
        data
    end

    def append_from_file(file)
        loaded_objects = Syck.load(file)

        raise "version number '#{loaded_objects[:version]}' should be '#{FILE_VERSION}'" unless loaded_objects[:version] == FILE_VERSION

        @missing_plugin_names.clear
        OBJECT_SYMBOLS.each { |obj_type|
            loaded_objects[obj_type] ||= []
            raise "expected array for #{obj_type.to_s} list (was #{loaded_objects[obj_type].class})" unless loaded_objects[obj_type].is_a? Array

            # clean out any YAML::Objects we find
            loaded_objects[obj_type].delete_if { |parent_object|
                if parent_object.is_a? Syck::Object
                    @missing_plugin_names << "#{parent_object.class} (object: #{parent_object.ivars['title']})"
                    true
                else
                    parent_object.effects.delete_if { |child_object|
                        if child_object.is_a? Syck::Object
                            @missing_plugin_names << "#{child_object.class} (in object: #{parent_object.title})"
                            true
                        else
                            false
                        end
                    } if parent_object.respond_to? :effects
                    false        # don't delete parent
                end
            }

            # add in
            instance_variable_get("@#{obj_type}").concat(loaded_objects[obj_type])
        }

        @last_save_time = Time.now
        $engine.reinitialize_user_objects
    end
end