filezor 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
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