parcel 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/parcel.rb +87 -0
- data/lib/parcel/has_parcel.rb +63 -0
- data/lib/parcel/zip_file_repository.rb +97 -0
- metadata +66 -0
data/lib/parcel.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "parcel", "has_parcel")
|
2
|
+
require File.join(File.dirname(__FILE__), "parcel", "zip_file_repository")
|
3
|
+
|
4
|
+
module Parcel
|
5
|
+
|
6
|
+
@options = Hash.new
|
7
|
+
|
8
|
+
def self.storage_root
|
9
|
+
@options[:storage_root] || "./:class_name/:id"
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.storage_root=(value)
|
13
|
+
@options[:storage_root] = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.temp_path(filename)
|
17
|
+
date_stamp = "%04d_%02d_%02d" % [Time.now.year, Time.now.month, Time.now.day]
|
18
|
+
path = File.join(temp_root, date_stamp, "#{Time.now.to_i}_#{rand(999999)}_#{filename}")
|
19
|
+
FileUtils.mkdir_p( File.dirname(path) )
|
20
|
+
path
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.temp_root=(value)
|
24
|
+
@options[:temp_root] = value
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.temp_root
|
28
|
+
@options[:temp_root] || "/tmp/parcel"
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.underscore(camel_cased_word)
|
32
|
+
camel_cased_word.to_s.gsub(/::/, '/').
|
33
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
34
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
35
|
+
tr("-", "_").
|
36
|
+
downcase
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.interpolate_path(path, options, reference)
|
40
|
+
new_path = path.to_s
|
41
|
+
|
42
|
+
framework_opts = Hash.new
|
43
|
+
if defined?(Rails)
|
44
|
+
framework_opts[:rails_root] = Rails.root
|
45
|
+
framework_opts[:id] = reference.id if reference.is_a?(ActiveRecord::Base)
|
46
|
+
end
|
47
|
+
|
48
|
+
framework_opts[:id] ||= reference.object_id
|
49
|
+
|
50
|
+
merge_opts = framework_opts.merge({ :class_name => underscore(reference.class.name) }).merge(options)
|
51
|
+
|
52
|
+
puts merge_opts.inspect
|
53
|
+
|
54
|
+
merge_opts.to_a.sort_by { |(k,v)| -k.to_s.length }.each do |(k,v)|
|
55
|
+
new_path = new_path.gsub(":#{k}", v.to_s)
|
56
|
+
end
|
57
|
+
|
58
|
+
new_path
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.create_repository(owner, options, source = nil)
|
62
|
+
repo = if options[:class] == :zip
|
63
|
+
ZipFileRepository.new(owner, options)
|
64
|
+
else
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
raise ArgumentError, "Repository not supported: #{options[:class]}" if repo.nil?
|
69
|
+
|
70
|
+
if source.is_a?(String) && source.starts_with?("/")
|
71
|
+
repo.import(File.open(source, "r"))
|
72
|
+
elsif source.is_a?(String)
|
73
|
+
repo.import_data(source)
|
74
|
+
elsif source.respond_to?(:to_file)
|
75
|
+
repo.import(source.to_file)
|
76
|
+
elsif source.respond_to?(:read)
|
77
|
+
repo.import(source)
|
78
|
+
elsif File.exist?(repo.commit_path)
|
79
|
+
repo.import(File.open(repo.commit_path, "r"))
|
80
|
+
end
|
81
|
+
|
82
|
+
repo
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
Object.send(:include, Parcel::HasParcel)
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Parcel
|
2
|
+
module HasParcel
|
3
|
+
def self.included(base)
|
4
|
+
base.module_eval do
|
5
|
+
extend ClassMethods
|
6
|
+
include InstanceMethods
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module InstanceMethods
|
11
|
+
def parcel_data_path(filename)
|
12
|
+
data_path = File.join(Parcel.interpolate_path(Parcel.storage_root, Hash.new, self), filename.to_s)
|
13
|
+
data_path
|
14
|
+
end
|
15
|
+
|
16
|
+
def commit_parcels!
|
17
|
+
Array(instance_variables.select { |v| v =~ /\@_parcel_/ }).each do |name|
|
18
|
+
instance = instance_variable_get(name)
|
19
|
+
instance.commit! if instance
|
20
|
+
end
|
21
|
+
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
def destroy_parcels!
|
26
|
+
FileUtils.rm_rf(self.class.parcel_data_path("."))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module ClassMethods
|
31
|
+
|
32
|
+
def has_parcel(name, options = {})
|
33
|
+
options = { :class => :zip }.merge(options).merge({ :name => name })
|
34
|
+
|
35
|
+
unless included_modules.include?(InstanceMethods)
|
36
|
+
include(InstanceMethods)
|
37
|
+
|
38
|
+
if defined?(ActiveRecord) && is_a?(ActiveRecord::Base)
|
39
|
+
after_save :commit_parcels!
|
40
|
+
after_destroy :destroy_parcels!
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
define_method(name.to_sym) do
|
45
|
+
instance_variable_get("@_parcel_#{name}") || begin
|
46
|
+
parcel = Parcel.create_repository(self, options)
|
47
|
+
instance_variable_set("@_parcel_#{name}", parcel)
|
48
|
+
parcel
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
define_method("#{name}=".to_sym) do |input|
|
53
|
+
instance = instance_variable_get("@_parcel_#{name}")
|
54
|
+
instance.destroy if instance
|
55
|
+
|
56
|
+
instance_variable_set("@_parcel_#{name}", Parcel.create_repository(self, options, input))
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'zip/zip'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
module Parcel
|
6
|
+
|
7
|
+
# The FileRepository class represents a compressed archive of files
|
8
|
+
# providing an easy interface for enumerating and updating the contents.
|
9
|
+
class ZipFileRepository
|
10
|
+
|
11
|
+
# Create a blank FileRepository, or open one from an existing file object.
|
12
|
+
def initialize(owner = nil, options = Hash.new)
|
13
|
+
@temp_file = Parcel.temp_path("#{owner.object_id}_#{rand(99999)}.zip")
|
14
|
+
@owner = owner
|
15
|
+
@name = options[:name]
|
16
|
+
end
|
17
|
+
|
18
|
+
def import(source)
|
19
|
+
File.open(@temp_file, "w") { |f| f.write source.read }
|
20
|
+
end
|
21
|
+
|
22
|
+
def import_data(data)
|
23
|
+
File.open(@temp_file, "w") { |f| f.write data }
|
24
|
+
end
|
25
|
+
|
26
|
+
def commit_path
|
27
|
+
@owner.parcel_data_path(@name) + ".zip"
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns true if there are no files in the repository, or if the attachment is nil.
|
31
|
+
def blank?
|
32
|
+
files.blank?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Clears the repository.
|
36
|
+
def clear!
|
37
|
+
File.unlink(@temp_file) if File.exist?(@temp_file)
|
38
|
+
|
39
|
+
Zip::ZipFile.open( @temp_file, Zip::ZipFile::CREATE ) do
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns all the files present in the repository.
|
44
|
+
def files
|
45
|
+
return [] if @temp_file.nil? || !File.exist?(@temp_file)
|
46
|
+
|
47
|
+
result = Array.new
|
48
|
+
Zip::ZipFile.foreach(@temp_file) { |entry| result << OpenStruct.new(:name => entry.name, :size => entry.size) }
|
49
|
+
result
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the contents of the given file. Supports wildcard matching,
|
53
|
+
# in which case returns the contents of the first file found.
|
54
|
+
def read_file(filename)
|
55
|
+
return nil if blank?
|
56
|
+
|
57
|
+
file_match = Regexp.new("^" + filename.gsub(".", "\\.").gsub("*", ".*").gsub("?", ".") + "$", Regexp::IGNORECASE)
|
58
|
+
|
59
|
+
Zip::ZipFile.foreach(@temp_file) do |entry|
|
60
|
+
if entry.name =~ file_match
|
61
|
+
read_file = Parcel.temp_path(File.basename(entry.name))
|
62
|
+
entry.extract(read_file)
|
63
|
+
result = IO.read(read_file)
|
64
|
+
File.unlink(read_file)
|
65
|
+
return result
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
return nil
|
70
|
+
end
|
71
|
+
|
72
|
+
# Adds a file to the repository.
|
73
|
+
def add_file(filename, contents)
|
74
|
+
Zip::ZipFile.open( @temp_file, Zip::ZipFile::CREATE ) do |writer|
|
75
|
+
writer.get_output_stream(filename) { |file| file.write contents }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns an open file stream of the repository.
|
80
|
+
def to_file
|
81
|
+
blank? ? nil : File.open(@temp_file)
|
82
|
+
end
|
83
|
+
|
84
|
+
def save_to(destination)
|
85
|
+
FileUtils.mkdir_p(File.dirname(destination))
|
86
|
+
FileUtils.cp(@temp_file, destination) if @temp_file && File.exist?(@temp_file)
|
87
|
+
end
|
88
|
+
|
89
|
+
def commit!
|
90
|
+
raise ArgumentError, "No owner defined, use \#save_to instead" if @owner == nil
|
91
|
+
save_to(commit_path)
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: parcel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sean St. Quentin
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-15 00:00:00 +11:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rubyzip
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: " Parcel is a simple library which allows normal ruby objects to maintain a series\n of files stored in one or more repositories which are accessible through normal ruby attributes.\n Possible repositories include Zip Files, Tar Files, Subversion Repos, Repos, GridFS Zips, etc.\n It also provides an common and easy to use interface to all these repositories (i.e. more ruby like than RubyZip), and\n works with ActiveRecord but doesn't depend on it.\n"
|
26
|
+
email:
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- lib/parcel.rb
|
35
|
+
- lib/parcel/has_parcel.rb
|
36
|
+
- lib/parcel/zip_file_repository.rb
|
37
|
+
has_rdoc: true
|
38
|
+
homepage: http://github.com/elseano/parcel
|
39
|
+
licenses: []
|
40
|
+
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options: []
|
43
|
+
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "0"
|
51
|
+
version:
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
version:
|
58
|
+
requirements: []
|
59
|
+
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 1.3.5
|
62
|
+
signing_key:
|
63
|
+
specification_version: 3
|
64
|
+
summary: Simple repository management
|
65
|
+
test_files: []
|
66
|
+
|