filezor 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ spec/tmp
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Kyle Maxwell
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,17 @@
1
+ = filezor
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 Kyle Maxwell. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "filezor"
8
+ gem.summary = %Q{pure ruby file sync}
9
+ gem.description = %Q{pure ruby file sync}
10
+ gem.email = "kyle@kylemaxwell.com"
11
+ gem.homepage = "http://github.com/fizx/filezor"
12
+ gem.authors = ["Kyle Maxwell"]
13
+ gem.add_dependency "trollop"
14
+ gem.add_dependency "rest-client", "~> 1.3"
15
+ gem.add_development_dependency "rspec", ">= 1.2.9"
16
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
21
+ end
22
+
23
+ def sys(s);puts s; system s; end
24
+
25
+ require 'spec/rake/spectask'
26
+ Spec::Rake::SpecTask.new(:spec) do |spec|
27
+ spec.libs << 'lib' << 'spec'
28
+ spec.spec_files = FileList['spec/**/*_spec.rb']
29
+ end
30
+
31
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
32
+ spec.libs << 'lib' << 'spec'
33
+ spec.pattern = 'spec/**/*_spec.rb'
34
+ spec.rcov = true
35
+ end
36
+
37
+ task :spec => :check_dependencies
38
+
39
+ task :default => :spec
40
+
41
+ task :push => :build do
42
+ abort "No HOST" unless ENV["HOST"]
43
+ version = File.exist?('VERSION') ? File.read('VERSION').strip : ""
44
+ sys "scp pkg/filezor-#{version}.gem #{ENV["HOST"]}:~"
45
+ sys "ssh #{ENV["HOST"]} 'sudo gem install filezor-#{version}.gem'"
46
+ end
47
+
48
+ require 'rake/rdoctask'
49
+ Rake::RDocTask.new do |rdoc|
50
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
51
+
52
+ rdoc.rdoc_dir = 'rdoc'
53
+ rdoc.title = "filezor #{version}"
54
+ rdoc.rdoc_files.include('README*')
55
+ rdoc.rdoc_files.include('lib/**/*.rb')
56
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.3.2
data/bin/filezor ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.dirname(__FILE__) + "/../lib"
3
+ require "rubygems"
4
+ require "trollop"
5
+ require "filezor"
6
+
7
+ usage = "Usage: #{$0} [options]"
8
+ opts = Trollop::options do
9
+ banner <<-EOS
10
+ Runs a filezor server
11
+
12
+ #{usage}
13
+
14
+ EOS
15
+ opt :root, "Filesystem root", :default => "/tmp"
16
+ opt :caching, "Turn caching on"
17
+ end
18
+
19
+ Filezor::Server.root = opts[:root]
20
+ Filezor::Server.caching = opts[:caching]
21
+
22
+ Filezor::Server.run!
data/config/filezor.ru ADDED
@@ -0,0 +1,10 @@
1
+ require 'sinatra'
2
+
3
+ set :environment, :production
4
+ set :run, false
5
+ set :lock, false
6
+
7
+ require "rubygems"
8
+ require 'filezor'
9
+
10
+ Filezor::Server.run!
@@ -0,0 +1,10 @@
1
+ ---
2
+ environment: production
3
+ address: 0.0.0.0
4
+ port: 1343
5
+ pid: /var/run/filezor.pid
6
+ log: /var/log/filezor.log
7
+ max_conns: 1024
8
+ timeout: 30
9
+ max_persistent_conns: 512
10
+ daemonize: true
data/filezor.gemspec ADDED
@@ -0,0 +1,78 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{filezor}
8
+ s.version = "1.3.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Kyle Maxwell"]
12
+ s.date = %q{2010-09-06}
13
+ s.default_executable = %q{filezor}
14
+ s.description = %q{pure ruby file sync}
15
+ s.email = %q{kyle@kylemaxwell.com}
16
+ s.executables = ["filezor"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.rdoc"
20
+ ]
21
+ s.files = [
22
+ ".document",
23
+ ".gitignore",
24
+ "LICENSE",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "bin/filezor",
29
+ "config/filezor.ru",
30
+ "config/filezor.yml",
31
+ "filezor.gemspec",
32
+ "lib/filezor.rb",
33
+ "lib/filezor/client.rb",
34
+ "lib/filezor/file.rb",
35
+ "lib/filezor/server.rb",
36
+ "lib/filezor/util.rb",
37
+ "spec/filezor/client_spec.rb",
38
+ "spec/filezor/server_spec.rb",
39
+ "spec/filezor/util_spec.rb",
40
+ "spec/fixtures/.password",
41
+ "spec/fixtures/a.txt",
42
+ "spec/fixtures/b.txt",
43
+ "spec/fixtures/z/f.txt",
44
+ "spec/spec.opts",
45
+ "spec/spec_helper.rb"
46
+ ]
47
+ s.homepage = %q{http://github.com/fizx/filezor}
48
+ s.rdoc_options = ["--charset=UTF-8"]
49
+ s.require_paths = ["lib"]
50
+ s.rubygems_version = %q{1.3.6}
51
+ s.summary = %q{pure ruby file sync}
52
+ s.test_files = [
53
+ "spec/filezor/client_spec.rb",
54
+ "spec/filezor/server_spec.rb",
55
+ "spec/filezor/util_spec.rb",
56
+ "spec/spec_helper.rb"
57
+ ]
58
+
59
+ if s.respond_to? :specification_version then
60
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
61
+ s.specification_version = 3
62
+
63
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
64
+ s.add_runtime_dependency(%q<trollop>, [">= 0"])
65
+ s.add_runtime_dependency(%q<rest-client>, ["~> 1.3"])
66
+ s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
67
+ else
68
+ s.add_dependency(%q<trollop>, [">= 0"])
69
+ s.add_dependency(%q<rest-client>, ["~> 1.3"])
70
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
71
+ end
72
+ else
73
+ s.add_dependency(%q<trollop>, [">= 0"])
74
+ s.add_dependency(%q<rest-client>, ["~> 1.3"])
75
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
76
+ end
77
+ end
78
+
data/lib/filezor.rb ADDED
@@ -0,0 +1,5 @@
1
+ module Filezor; end
2
+ require "filezor/client"
3
+ require "filezor/server"
4
+ require "filezor/util"
5
+ require "filezor/file"
@@ -0,0 +1,106 @@
1
+ require "net/http"
2
+ require "cgi"
3
+ require "set"
4
+ require "restclient"
5
+ require "filezor/file"
6
+ require "json"
7
+
8
+ class Filezor::Client
9
+ class Unauthorized < RuntimeError; end
10
+ class InvalidArgument < RuntimeError; end
11
+
12
+ attr_reader :options
13
+
14
+ def initialize(host, port, password, options = {})
15
+ @root = "http://admin:#{password}@#{host}:#{port}"
16
+ @options ||= options
17
+ @options.update JSON.parse(post("info", options.to_json, "/"))
18
+ end
19
+
20
+ # This method acts like an rsync --delete. It deletes all files in the root,
21
+ # and then puts the new files in. TODO: make atomic
22
+ def sync(*files)
23
+ options = files.last.is_a?(Hash) ? files.pop : {}
24
+ files = files.flatten.uniq
25
+ test(files)
26
+ #
27
+ # files.each do |f|
28
+ # f.path = File.join(root, f.path)
29
+ # end
30
+ cached_md5s = caching? ? Set.new(cached(files)) : []
31
+ params = files.inject({}) do |memo, file|
32
+ memo[file.path] = cached_md5s.include?(file.md5) ? file.md5 : file.tempfile
33
+ memo
34
+ end
35
+
36
+ params["--delete"] = options[:delete] if options[:delete]
37
+ params["--root"] = options[:root] if options[:root]
38
+ post("", params)
39
+ end
40
+
41
+ # Simple file upload
42
+ def put(*files)
43
+ test(files)
44
+ params = files.flatten.inject({}) do |memo, file|
45
+ memo[file.path] = file.tempfile
46
+ memo
47
+ end
48
+ post("", params)
49
+ end
50
+
51
+ def set_options(options = {})
52
+ @options = JSON.parse(post("info", options.to_json, "/"))
53
+ end
54
+
55
+ def ping
56
+ get("ping", "/").code == 200
57
+ end
58
+
59
+ def cached(*files)
60
+ JSON.parse(post("cached", files.flatten.map{|f| f.md5 }.to_json, "/"))
61
+ end
62
+
63
+ def get(path, prefix = "/file/")
64
+ _get("#{http_root}#{prefix}#{path}")
65
+ rescue RestClient::Unauthorized
66
+ raise Unauthorized
67
+ end
68
+
69
+ def post(path, params, prefix = "/file/")
70
+ _post("#{http_root}#{prefix}#{path}", params)
71
+ rescue RestClient::Unauthorized
72
+ raise Unauthorized
73
+ end
74
+
75
+ def http_root
76
+ "#{@root}#{app_root}"
77
+ end
78
+
79
+ def app_root
80
+ options["app_root"] || ""
81
+ end
82
+
83
+ def caching?
84
+ options["caching"]
85
+ end
86
+
87
+ private
88
+
89
+ def _get(*args)
90
+ puts args.inspect if ENV["FILEZOR_DEBUG"]
91
+ RestClient.get(*args)
92
+ end
93
+
94
+ def _post(*args)
95
+ puts args.inspect if ENV["FILEZOR_DEBUG"]
96
+ RestClient.post(*args)
97
+ end
98
+
99
+ def test(*files)
100
+ files.flatten.each do |file|
101
+ unless file.is_a?(Filezor::File)
102
+ raise InvalidArgument.new(file.inspect)
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,11 @@
1
+ require "filezor/util"
2
+ # A File that knows its MD5 hash, and target upload path.
3
+ class Filezor::File
4
+ attr_accessor :tempfile, :md5, :path
5
+
6
+ def initialize(tempfile, path, hash = nil)
7
+ @tempfile = tempfile
8
+ @path = path
9
+ @md5 = hash || Filezor::Util::md5(tempfile)
10
+ end
11
+ end
@@ -0,0 +1,210 @@
1
+ require "sinatra"
2
+ require "cgi"
3
+ require "fileutils"
4
+ require "json"
5
+ require "filezor/util"
6
+ class Filezor::Server < Sinatra::Base
7
+ class BadRequest < RuntimeError; end
8
+ include FileUtils
9
+ include Filezor::Util
10
+
11
+ PATH_ROUTE = %r[/file(/.+)]
12
+ PATH_ROUTE_ROOT = %r[/file/?]
13
+ CACHED = %r[/cached/?]
14
+ PASSWORD = %r[/auth/?]
15
+ INFO = %r[/info/?]
16
+ PING = %r[/ping/?]
17
+
18
+ use Rack::Auth::Basic do |username, password|
19
+ authenticate(username, password)
20
+ end
21
+
22
+ use Rack::CommonLogger
23
+
24
+ get INFO do
25
+ JSON.generate("caching" => caching?, "root" => root)
26
+ end
27
+
28
+ post INFO do
29
+ json = JSON.parse(request.body.read)
30
+ self.class.root = json["root"] unless json["root"].nil?
31
+ self.class.caching = json["caching"] unless json["caching"].nil?
32
+ JSON.generate("caching" => caching?, "root" => root)
33
+ end
34
+
35
+ get PATH_ROUTE do
36
+ path = path_of(params[:captures].first)
37
+ if File.file?(path)
38
+ File.read(path)
39
+ else
40
+ 404
41
+ end
42
+ end
43
+
44
+ get PING do
45
+ "ok"
46
+ end
47
+
48
+ delete PATH_ROUTE do
49
+ path = path_of(params[:captures].first)
50
+ rm_rf path
51
+ "ok"
52
+ end
53
+
54
+ post PATH_ROUTE_ROOT do
55
+ begin
56
+ chroot = params.delete("--root")
57
+ if globs = params.delete("--delete")
58
+ globs.split(",").each do |relative|
59
+ path = path_of(relative, chroot)
60
+ if path == root
61
+ raise BadRequest.new("Can't delete root")
62
+ end
63
+ rm_rf Dir[path]
64
+ end
65
+ end
66
+
67
+ params.each do |relative, value|
68
+ path = path_of(relative, chroot)
69
+ if value.is_a?(Hash) && value[:filename]
70
+ # Uploading the file
71
+ mkdir_p(File.dirname(path))
72
+ if caching?
73
+ # Move it to the cache and symlink into place
74
+ mkdir_p cache
75
+ cached_file = File.join(cache, md5(value[:tempfile]))
76
+ mv value[:tempfile].path, cached_file
77
+ ln_sf cached_file, path
78
+ else
79
+ # Just dump it in the target
80
+ mv value[:tempfile].path, path
81
+ end
82
+ elsif !caching?
83
+ raise BadRequest # can't issue a cache request if not in that mode
84
+ else
85
+ # Symlink to the existing file in the cache
86
+ cached_file = File.join(cache, value)
87
+ mkdir_p(File.dirname(path))
88
+ ln_sf cached_file, path
89
+ end
90
+ end
91
+ "ok"
92
+ rescue BadRequest
93
+ 400
94
+ end
95
+ end
96
+
97
+ post CACHED do
98
+ JSON.parse(request.body.read).select do |md5|
99
+ File.exists?(File.join(cache, md5))
100
+ end.to_json
101
+ end
102
+
103
+ post PASSWORD do
104
+ self.class.password = request.body.read
105
+ "ok"
106
+ end
107
+
108
+ not_found do
109
+ "not found"
110
+ end
111
+
112
+ error do
113
+ "error: #{request.env['sinatra.error'].message}"
114
+ end
115
+
116
+ def path_of(relative, chroot = nil)
117
+ new_root = case chroot
118
+ when nil; root
119
+ when /^\//; chroot
120
+ else; File.join(root, chroot)
121
+ end
122
+ new_path = File.expand_path(File.join(new_root, CGI::unescape(relative)))
123
+ unless new_path[0, root.length] == root
124
+ raise "Can't leave root"
125
+ end
126
+ new_path
127
+ end
128
+
129
+ def cache
130
+ File.join(root, ".cache")
131
+ end
132
+
133
+ def caching?
134
+ self.class.caching
135
+ end
136
+
137
+ def root
138
+ self.class.root
139
+ end
140
+
141
+ module ClassMethods
142
+ attr_reader :root
143
+
144
+ def caching
145
+ !!@caching
146
+ end
147
+
148
+ def root=(root)
149
+ @root = File.expand_path(root)
150
+ end
151
+
152
+ def caching=(caching)
153
+ @caching = caching
154
+ end
155
+
156
+ def authenticate(user, pwd)
157
+ # puts "#{pwd} == #{password}"
158
+ pwd == password
159
+ end
160
+
161
+ def password
162
+ @password ||= begin
163
+ tmp = File.exists?(password_file) ?
164
+ File.read(password_file) :
165
+ "default"
166
+ tmp
167
+ end
168
+ end
169
+
170
+ def password=(pwd)
171
+ File.open(password_file, "w") {|f| f.print(pwd) }
172
+ @password = pwd
173
+ end
174
+
175
+ def password_file
176
+ File.join(root, ".password")
177
+ end
178
+
179
+ # Helpful for testing clients
180
+ def start_embedded!(root, password)
181
+ ppid = Process.pid
182
+ Filezor::Server.root = root
183
+ Filezor::Server.password = password
184
+ fork do
185
+ harikari(ppid)
186
+ STDOUT.reopen("/dev/null")
187
+ Filezor::Server.run!
188
+ end
189
+
190
+ sleep 1
191
+ end
192
+
193
+ private
194
+
195
+ def harikari(ppid)
196
+ Thread.new do
197
+ loop do
198
+ begin
199
+ Process.kill(0, ppid)
200
+ rescue
201
+ exit
202
+ end
203
+ sleep 1
204
+ end
205
+ end
206
+ end
207
+ end
208
+ extend ClassMethods
209
+
210
+ end
@@ -0,0 +1,23 @@
1
+ require "digest/md5"
2
+ require "stringio"
3
+ module Filezor::Util
4
+ BUFFER_SIZE = 2**16
5
+
6
+ # Streaming MD5 on IO objects
7
+ def md5(io)
8
+ md5 = Digest::MD5.new
9
+ while chunk = io.read(BUFFER_SIZE)
10
+ md5 << chunk
11
+ end
12
+ io.rewind
13
+ md5.hexdigest
14
+ end
15
+
16
+ def files_from_map(map)
17
+ map.inject([]) do |memo, (path, value)|
18
+ memo << Filezor::File.new(StringIO.new(value), path)
19
+ end
20
+ end
21
+
22
+ extend self
23
+ end
@@ -0,0 +1,107 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ fixtures = File.dirname(__FILE__) + "/../fixtures/."
4
+ @root = File.dirname(__FILE__) + "/../tmp/filezor-test"
5
+ rm_rf @root
6
+ mkdir_p @root
7
+ cp_r fixtures, @root
8
+
9
+ Filezor::Server.start_embedded!(@root, "hiworld")
10
+
11
+ describe Filezor::Client do
12
+
13
+ before do
14
+ @pass = "hiworld"
15
+ @root = File.dirname(__FILE__) + "/../tmp/filezor-test"
16
+ @client = Filezor::Client.new("localhost", 4567, @pass)
17
+ end
18
+
19
+ it "should be able to deal with a server on a subpath" do
20
+ @client = Filezor::Client.new("localhost", 4567, @pass, "app_root" => "/filer")
21
+ @client.should_receive(:_get).with("http://admin:hiworld@localhost:4567/filer/ping").and_return(mock(:code => 200))
22
+ @client.ping
23
+ end
24
+
25
+ context "caching on" do
26
+ before do
27
+ @client.set_options("caching" => true, "root" => @root)
28
+ @file = Filezor::File.new(fixture_file("a.txt"), "x/y.txt")
29
+ @client.put(@file)
30
+ end
31
+
32
+ it "should have an md5" do
33
+ @file.md5.should == "11e561e2cfcd0ac2b4bb0089a665b031"
34
+ end
35
+
36
+ it "should say its cached" do
37
+ @client.cached(@file).should == ["11e561e2cfcd0ac2b4bb0089a665b031"]
38
+ end
39
+
40
+ describe "#sync" do
41
+ it "should send the md5, not the file" do
42
+ @file = Filezor::File.new(fixture_file("a.txt"), "b.txt")
43
+ @client.should_receive(:cached).and_return(["11e561e2cfcd0ac2b4bb0089a665b031"])
44
+ @client.should_receive(:post).with("", {"--delete" => "x", "x/b.txt" => "11e561e2cfcd0ac2b4bb0089a665b031" })
45
+ @client.sync("x", @file)
46
+ end
47
+ end
48
+ end
49
+
50
+ [false, true].each do |caching_state|
51
+ context "Caching is #{caching_state.inspect}" do
52
+ before do
53
+ @client.set_options("caching" => caching_state, "root" => @root)
54
+ end
55
+
56
+ it "should receive options" do
57
+ @client.options.should be_a(Hash)
58
+ @client.options["caching"].should == caching_state
59
+ end
60
+
61
+ it "should be able to ping" do
62
+ @client.ping.should be_true
63
+ end
64
+
65
+ it "should get errors on bad password" do
66
+ proc do
67
+ @client = Filezor::Client.new("localhost", 4567, "wrongpassword")
68
+ @client.get("/anything")
69
+ end.should raise_error(Filezor::Client::Unauthorized)
70
+ end
71
+
72
+ describe "#sync" do
73
+ before do
74
+ @path = "a/b/c.txt"
75
+ mkdir_p "#{@root}/a/b"
76
+ @unwanted = "#{@root}/a/b/gone.txt"
77
+ File.open(@unwanted, "w"){|f| f.puts "hi" }
78
+ file = Filezor::File.new(fixture_file("a.txt"), "c.txt")
79
+ @client.sync("a/b", file)
80
+ end
81
+
82
+ it "should upload the files" do
83
+ File.exists?(File.join(@root, @path)).should be_true
84
+ end
85
+
86
+ it "should clear out other files in the folder" do
87
+ File.exists?(@unwanted).should be_false
88
+ end
89
+ end
90
+
91
+ describe "#put" do
92
+ it "should only take Filezor::Files" do
93
+ proc do
94
+ @client.put("something")
95
+ end.should raise_error(Filezor::Client::InvalidArgument)
96
+ end
97
+
98
+ it "should upload the files" do
99
+ path = "a/b/c.txt"
100
+ file = Filezor::File.new(fixture_file("a.txt"), path)
101
+ @client.put(file)
102
+ File.exists?(File.join(@root, path)).should be_true
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,213 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Filezor::Server do
4
+ include Rack::Test::Methods
5
+
6
+ def app
7
+ Filezor::Server
8
+ end
9
+
10
+ def login(user = "admin", pass = "hiworld")
11
+ basic_authorize user, pass
12
+ end
13
+
14
+ before do
15
+ fixtures = File.dirname(__FILE__) + "/../fixtures/."
16
+ @root = File.dirname(__FILE__) + "/../tmp/filezor-test"
17
+ rm_rf @root
18
+ mkdir_p @root
19
+ cp_r fixtures, @root
20
+ app.root = @root
21
+ app.password = "hiworld"
22
+ app.caching = false
23
+ end
24
+
25
+ context "without auth" do
26
+ it "should not work" do
27
+ get "/"
28
+ last_response.status.should == 401
29
+ post "/asdfasdf/fds/d/dd"
30
+ last_response.status.should == 401
31
+ end
32
+ end
33
+
34
+ context "with incorrect login" do
35
+ it "should complain" do
36
+ login("admin", "wrong")
37
+ get "/file/a.txt"
38
+ last_response.status.should == 401
39
+ end
40
+ end
41
+
42
+ context "with auth" do
43
+ before do
44
+ login
45
+ end
46
+
47
+ it "should be able to get a file" do
48
+ get "/file/a.txt"
49
+ last_response.should be_ok
50
+ last_response.body.should == fixture_file("a.txt").read
51
+ end
52
+
53
+ it "should be able to ping" do
54
+ get "/ping"
55
+ last_response.body.should == "ok"
56
+ end
57
+
58
+ it "should 404 on file not found" do
59
+ get "/file/what.txt"
60
+ last_response.status.should == 404
61
+ end
62
+
63
+ describe "posting files" do
64
+ before do
65
+ post "/file", "hi.txt" => upload_fixture_file("a.txt")
66
+ last_response.should be_ok
67
+ last_response.body.should == "ok"
68
+ end
69
+
70
+ it "should be readable" do
71
+ get "/file/hi.txt"
72
+ last_response.body.should == fixture_file("a.txt").read
73
+ end
74
+
75
+ it "should fail on cached posts" do
76
+ post "/file", "what.txt" => "11e561e2cfcd0ac2b4bb0089a665b031"
77
+ last_response.status.should == 400
78
+ end
79
+ end
80
+
81
+ describe "posting files" do
82
+ before do
83
+ post "/file", "hi.txt" => upload_fixture_file("a.txt")
84
+ last_response.should be_ok
85
+ last_response.body.should == "ok"
86
+ end
87
+
88
+ it "should be readable" do
89
+ get "/file/hi.txt"
90
+ last_response.body.should == fixture_file("a.txt").read
91
+ end
92
+
93
+ it "should fail on cached posts" do
94
+ post "/file", "what.txt" => "11e561e2cfcd0ac2b4bb0089a665b031"
95
+ last_response.status.should == 400
96
+ end
97
+ end
98
+
99
+ it "should be able to chroot deeper" do
100
+ post "/file", "--root" => "omg/i/m/deeper", "a.txt" => upload_fixture_file("a.txt")
101
+ get "/file/omg/i/m/deeper/a.txt"
102
+ last_response.status.should == 200
103
+ end
104
+
105
+ it "should be able to delete with a post" do
106
+ post "/file", "a.txt" => upload_fixture_file("a.txt"), "b.txt" => upload_fixture_file("b.txt")
107
+ get "/file/b.txt"
108
+ last_response.status.should == 200
109
+ post "/file", "--delete" => "*", "a.txt" => upload_fixture_file("a.txt")
110
+ get "/file/b.txt"
111
+ last_response.status.should == 404
112
+ end
113
+
114
+ it "should be able to delete with a post and chroot" do
115
+ post "/file", "--root" => "omg/i/m/deeper", "a.txt" => upload_fixture_file("a.txt"), "b.txt" => upload_fixture_file("b.txt")
116
+ get "/file/omg/i/m/deeper/b.txt"
117
+ last_response.status.should == 200
118
+ post "/file", "--root" => "omg/i/m/deeper", "--delete" => "*", "a.txt" => upload_fixture_file("a.txt")
119
+ get "/file/omg/i/m/deeper/b.txt"
120
+ last_response.status.should == 404
121
+ end
122
+
123
+
124
+ it "should be able to post a few files" do
125
+ post "/file", "a.txt" => upload_fixture_file("a.txt"), "b.txt" => upload_fixture_file("b.txt")
126
+ last_response.should be_ok
127
+ last_response.body.should == "ok"
128
+ get "/file/a.txt"
129
+ last_response.body.should == fixture_file("a.txt").read
130
+ get "/file/b.txt"
131
+ last_response.body.should == fixture_file("b.txt").read
132
+ end
133
+
134
+ it "should be able to delete a file" do
135
+ get "/file/a.txt"
136
+ last_response.body.should == fixture_file("a.txt").read
137
+ delete "/file/a.txt"
138
+ last_response.body.should == "ok"
139
+ get "/file/a.txt"
140
+ last_response.status.should == 404
141
+ end
142
+
143
+ it "should be able to set the password" do
144
+ post "/auth", "newpwd"
145
+ last_response.body.should == "ok"
146
+
147
+ get "/file/a.txt"
148
+ last_response.status.should == 401
149
+
150
+ login("admin", "newpwd")
151
+
152
+ get "/file/a.txt"
153
+ last_response.status.should == 200
154
+
155
+ post "/auth", @pass = "hiworld"
156
+ last_response.body.should == "ok"
157
+ end
158
+
159
+ it "should respond with info" do
160
+ get "/info"
161
+ JSON.parse(last_response.body)["caching"].should === false
162
+ end
163
+
164
+ it "should be able to modify info" do
165
+ post "/info", {"caching" => true }.to_json
166
+ JSON.parse(last_response.body)["caching"].should === true
167
+
168
+ get "/info"
169
+ JSON.parse(last_response.body)["caching"].should === true
170
+ end
171
+
172
+ context "in caching mode" do
173
+ before do
174
+ app.caching = true
175
+ post "/file", "hi.txt" => upload_fixture_file("a.txt")
176
+ last_response.should be_ok, last_response.inspect
177
+ end
178
+
179
+ it "should have cached the file" do
180
+ File.exists?(File.join(app.root, ".cache", "11e561e2cfcd0ac2b4bb0089a665b031")).should be_true
181
+ File.symlink?(File.join(app.root, "hi.txt")).should be_true
182
+ end
183
+
184
+ it "should be able to query the cache" do
185
+ post "/cached", %w[11e561e2cfcd0ac2b4bb0089a665b031 not_there].to_json
186
+ last_response.status.should == 200
187
+ last_response.body.should == ["11e561e2cfcd0ac2b4bb0089a665b031"].to_json
188
+ end
189
+
190
+ it "should be able to do extra symlinks" do
191
+ post "/file", "from_md5.txt" => "11e561e2cfcd0ac2b4bb0089a665b031"
192
+ target = File.join(app.root, "from_md5.txt")
193
+ File.symlink?(target).should be_true
194
+ File.read(target).should == fixture_file("a.txt").read
195
+ end
196
+
197
+ it "should be able to wipe out the old files" do
198
+ post "/file", "z/from_md5.txt" => "11e561e2cfcd0ac2b4bb0089a665b031", "--delete" => "/z"
199
+ last_response.status.should == 200
200
+ File.exists?(File.join(app.root, "z", "f.txt")).should be_false
201
+ File.exists?(File.join(app.root, "z", "from_md5.txt")).should be_true
202
+ end
203
+
204
+ it "should not be able to delete the root" do
205
+ post "/file", "from_md5.txt" => "11e561e2cfcd0ac2b4bb0089a665b031", "--delete" => "/"
206
+ last_response.status.should == 400
207
+ File.exists?(File.join(app.root, "a.txt")).should be_true
208
+ end
209
+
210
+ end
211
+ end
212
+
213
+ end
@@ -0,0 +1,24 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ require "filezor"
4
+ describe "Filezor::Util" do
5
+ include Rack::Test::Methods
6
+
7
+ describe "#md5" do
8
+ it "should md5 a stream" do
9
+ Filezor::Util.md5(StringIO.new("hello")).should == Digest::MD5.hexdigest("hello")
10
+ end
11
+ end
12
+
13
+ describe "#files_from_map" do
14
+ it "should convert" do
15
+ map = { "foo/bar" => "hello"}
16
+ files = Filezor::Util.files_from_map(map)
17
+ files.size.should == 1
18
+ file = files.first
19
+ file.path.should == "foo/bar"
20
+ file.tempfile.read.should == "hello"
21
+ file.md5.should == Digest::MD5.hexdigest("hello")
22
+ end
23
+ end
24
+ end
@@ -0,0 +1 @@
1
+ hi world
@@ -0,0 +1 @@
1
+ C'est un file
File without changes
File without changes
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,21 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'rubygems'
4
+ require 'filezor'
5
+ require 'spec'
6
+ require 'spec/autorun'
7
+ require 'rack/test'
8
+ require 'fileutils'
9
+ include FileUtils
10
+
11
+ Spec::Runner.configure do |config|
12
+
13
+ end
14
+
15
+ def fixture_file(name)
16
+ File.open(File.dirname(__FILE__) + "/fixtures/#{name}")
17
+ end
18
+
19
+ def upload_fixture_file(name)
20
+ Rack::Test::UploadedFile.new(fixture_file(name).path, "application/binary")
21
+ end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: filezor
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 3
8
+ - 2
9
+ version: 1.3.2
10
+ platform: ruby
11
+ authors:
12
+ - Kyle Maxwell
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-09-06 00:00:00 -07:00
18
+ default_executable: filezor
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: trollop
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :runtime
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: rest-client
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ~>
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 1
41
+ - 3
42
+ version: "1.3"
43
+ type: :runtime
44
+ version_requirements: *id002
45
+ - !ruby/object:Gem::Dependency
46
+ name: rspec
47
+ prerelease: false
48
+ requirement: &id003 !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ segments:
53
+ - 1
54
+ - 2
55
+ - 9
56
+ version: 1.2.9
57
+ type: :development
58
+ version_requirements: *id003
59
+ description: pure ruby file sync
60
+ email: kyle@kylemaxwell.com
61
+ executables:
62
+ - filezor
63
+ extensions: []
64
+
65
+ extra_rdoc_files:
66
+ - LICENSE
67
+ - README.rdoc
68
+ files:
69
+ - .document
70
+ - .gitignore
71
+ - LICENSE
72
+ - README.rdoc
73
+ - Rakefile
74
+ - VERSION
75
+ - bin/filezor
76
+ - config/filezor.ru
77
+ - config/filezor.yml
78
+ - filezor.gemspec
79
+ - lib/filezor.rb
80
+ - lib/filezor/client.rb
81
+ - lib/filezor/file.rb
82
+ - lib/filezor/server.rb
83
+ - lib/filezor/util.rb
84
+ - spec/filezor/client_spec.rb
85
+ - spec/filezor/server_spec.rb
86
+ - spec/filezor/util_spec.rb
87
+ - spec/fixtures/.password
88
+ - spec/fixtures/a.txt
89
+ - spec/fixtures/b.txt
90
+ - spec/fixtures/z/f.txt
91
+ - spec/spec.opts
92
+ - spec/spec_helper.rb
93
+ has_rdoc: true
94
+ homepage: http://github.com/fizx/filezor
95
+ licenses: []
96
+
97
+ post_install_message:
98
+ rdoc_options:
99
+ - --charset=UTF-8
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ segments:
107
+ - 0
108
+ version: "0"
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ segments:
114
+ - 0
115
+ version: "0"
116
+ requirements: []
117
+
118
+ rubyforge_project:
119
+ rubygems_version: 1.3.6
120
+ signing_key:
121
+ specification_version: 3
122
+ summary: pure ruby file sync
123
+ test_files:
124
+ - spec/filezor/client_spec.rb
125
+ - spec/filezor/server_spec.rb
126
+ - spec/filezor/util_spec.rb
127
+ - spec/spec_helper.rb