module RubyExt class FileSystemProvider attr_accessor :directories def initialize directories = lambda{[File.expand_path('.')]} @directories = directories end def class_get class_name path = real_class_path class_name raise Resource::NotExist unless path if File.directory? path "module #{File.basename(path)}; end;" else File.read path end end # It updates existing class, if class doesn't exist it creates one in directory provided by filter def class_set class_name, data, directory_for_class_file = nil directory_for_class_file ||= Dir.getwd directory_for_class_file = File.expand_path directory_for_class_file unless @directories.call.include? directory_for_class_file raise "Dirctories should include '#{directory_for_class_file}'!" end path = real_class_path class_name unless path path = class_to_files(class_name).select{|f| f.include?(directory_for_class_file)}.first dir = path.sub(/\w+\.rb/, "") FileUtils.mkdir dir unless dir.empty? or File.exist? dir end length = File.write(path, data) FileSystemProvider.remember_file path return length end def class_exist? class_name real_class_path(class_name) != nil end def class_delete class_name path = real_class_path class_name File.delete path if path end # def class_namespace_exist? namespace_name # File.exist? class_to_basefile(namespace_name) # end # First search for "./class_name.resource_name" files # And then search for "./class_name.res/resource_name" files def resource_get class_name, resource_name path = real_resource_path class_name, resource_name raise Resource::NotExist unless path File.read path end def resource_delete class_name, resource_name path = real_resource_path class_name, resource_name File.delete path if path end # It can only update existing resource, it can't create a new one # First search for the same resource and owerwrites it # If such resource doesn't exists writes to # "./class_name.res/resource_name" file. def resource_set class_name, resource_name, data class_path = real_class_path class_name raise Resource::NotExist unless class_path path = real_resource_path class_name, resource_name unless path class_path = class_path.sub(/\.rb$/, "") dir = "#{class_path}.res" FileUtils.mkdir dir unless File.exist? dir path = "#{dir}/#{resource_name}" end length = File.write path, data FileSystemProvider.remember_file path return length end def resource_exist? class_name, resource_name class_path = real_class_path class_name raise Resource::NotExist unless class_path real_resource_path(class_name, resource_name) != nil end # def class_to_virtual_path class_name # result = nil # if @cache.include? class_name # result = @cache[class_name] # else # result = nil # path = "#{base_dir}/#{class_name.gsub("::", "/")}" # if File.exist? path # result = path # else # path2 = "#{path}.rb" # if File.exist? path2 # result = path # else # path3 = "#{path}.res" # if File.exist? path3 # result = path # end # end # end # @cache[class_name] = result # end # # if result # return result # else # raise Resource::NotExist, "Class '#{class_name}' doesn't Exist!" # end # end # Different Providers can use different class path interpretation. # So we return virtual path only if this path really exist. def class_to_virtual_file class_name path = nil if FileSystemProvider.cache.include? class_name path = FileSystemProvider.cache[class_name] else path = real_class_path class_name raise Resource::NotExist, "Class '#{class_name}' doesn't Exist!" unless path FileSystemProvider.cache[class_name] = path end File.expand_path path end # Reloading @files, @cache = {}, {} class << self attr_reader :cache def start_watching interval = 2, directories = Dir.glob("**/lib") stop_watching @watch_thread = Thread.new do reset_changed_files directories while true sleep interval begin check_for_changed_files(directories).each do |type, klass, res| Resource.notify_observers :update_resource, type, klass, res end rescue Exception => e warn e end end end end def stop_watching if @watch_thread @watch_thread.kill @watch_thread = nil end end def remember_file path @files[path] = File.mtime(path) end protected def check_for_changed_files directories changed = [] directories.inject([]){|list, dir| list + Dir.glob("#{dir}/**/**")}.each do |path| if file_changed? path, directories remember_file path changed << file_changed(path, directories) end end return changed end def reset_changed_files directories @files = {} directories.inject([]){|list, dir| list + Dir.glob("#{dir}/**/**")}.each do |path| remember_file path end end def file_changed? path, directories old_time = @files[path] old_time == nil or old_time != File.mtime(path) end def file_changed path, directories begin if path =~ /\.rb$/ path = path.sub(/\.rb$/, "") class_name = path_to_class(path, directories) # ClassLoader.reload_class() klass = eval class_name, TOPLEVEL_BINDING, __FILE__, __LINE__ cache.delete class_name return :class, klass, nil else if path =~ /\.res/ class_path = path.sub(/\.res.+/, "") resource_name3 = path.sub("#{class_path}.res/", "") class_name = path_to_class class_path, directories else resource_name3 = path.sub(/.+\./, "") class_name = path_to_class path.sub(/\.#{resource_name3}$/, ""), directories end klass = eval class_name, TOPLEVEL_BINDING, __FILE__, __LINE__ return :resource, klass, resource_name3 end rescue Exception => e warn "Can't reload file '#{path}' #{e.message}!" end end def path_to_class path, directories base_dir = directories.select{|f| path.include? f}.max{|a, b| a.size <=> b.size} path.gsub(/^#{base_dir}\//, "").gsub("/", "::") end end protected def real_class_path class_name path = class_to_files(class_name).find{|f| File.exist? f} path = class_to_basefiles(class_name).find{|f| File.exist? f} unless path return path end def real_resource_path class_name, resource_name file = class_to_basefiles(class_name).collect{|f| "#{f}.#{resource_name}"}.find{|f| File.exist? f} file = class_to_basefiles(class_name).collect{|f| "#{f}.res/#{resource_name}"}.find{|f| File.exist? f} unless file return file end def class_to_files class_name class_to_basefiles(class_name).collect{|name| "#{name}.rb"} end # Different Providers can use different class path interpretation. # So we return virtual path only if this path really exist. def class_to_basefiles class_name relative = class_name.gsub("::", "/") return @directories.call.collect{|dir| "#{dir}/#{relative}"} end # Doesn't make Sense for Virtual Resource System # def resource_path class_name, resource_name # @monitor.synchronize do # path = "#{class_to_path(class_name)}.#{resource_name}" # if File.exist? path # return path # else # path = "#{class_to_path(class_name)}.res/#{resource_name}" # if File.exist? path # return path # else # nil # end # end # end # end end end