syncftp 0.0.1

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
+ examples
21
+
22
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Gregoire Lejeune
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,33 @@
1
+ = syncftp
2
+
3
+ Sync via FTP, only modified files
4
+
5
+ == ChangeLog
6
+
7
+ === 0.0.1 :
8
+ * First Release
9
+
10
+ == Example
11
+
12
+ require 'syncftp'
13
+
14
+ ftp = SyncFTP.new( 'localhost', :username => "MyUser", :password => "********" )
15
+ ftp.sync( :local => "rootdir", :remote => "temp/syncfs/test" )
16
+
17
+ == Note on Patches/Pull Requests
18
+
19
+ * Fork the project.
20
+ * Make your feature addition or bug fix.
21
+ * Add tests for it. This is important so I don't break it in a
22
+ future version unintentionally.
23
+ * Commit, do not mess with rakefile, version, or history.
24
+ (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)
25
+ * Send me a pull request. Bonus points for topic branches.
26
+
27
+ == Inspiration
28
+
29
+ This lib was truly inspired by the awsome glynn (http://github.com/dmathieu/glynn)
30
+
31
+ == Copyright
32
+
33
+ Copyright (c) 2010 Gregoire Lejeune. 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 = "syncftp"
8
+ gem.summary = %Q{Sync via FTP, only modified files}
9
+ gem.description = %Q{Sync via FTP, only modified files}
10
+ gem.email = "gregoire.lejeune@free.fr"
11
+ gem.homepage = "http://github.com/glejeune/syncftp"
12
+ gem.authors = ["Gregoire Lejeune"]
13
+ gem.add_dependency 'mime-types', ">= 0"
14
+
15
+ gem.add_development_dependency "shoulda", ">= 0"
16
+ gem.add_development_dependency "fakefs", ">= 0"
17
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
18
+ end
19
+ Jeweler::GemcutterTasks.new
20
+ rescue LoadError
21
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
22
+ end
23
+
24
+ require 'rake/testtask'
25
+ Rake::TestTask.new(:test) do |test|
26
+ test.libs << 'lib' << 'test'
27
+ test.pattern = 'test/**/test_*.rb'
28
+ test.verbose = true
29
+ end
30
+
31
+ begin
32
+ require 'rcov/rcovtask'
33
+ Rcov::RcovTask.new do |test|
34
+ test.libs << 'test'
35
+ test.pattern = 'test/**/test_*.rb'
36
+ test.verbose = true
37
+ end
38
+ rescue LoadError
39
+ task :rcov do
40
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
41
+ end
42
+ end
43
+
44
+ task :test => :check_dependencies
45
+
46
+ task :default => :test
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 = "syncftp #{version}"
54
+ rdoc.rdoc_files.include('README*')
55
+ rdoc.rdoc_files.include('lib/**/*.rb')
56
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,4 @@
1
+ $:.unshift( "../lib" )
2
+ require 'syncftp'
3
+
4
+ SyncFTP.new( 'localhost', :loglevel => Logger::DEBUG ).sync( :local => "rootdir", :remote => "temp/syncfs/test" )
data/lib/syncftp.rb ADDED
@@ -0,0 +1,207 @@
1
+ # Copyright (c) 2009 Gregoire Lejeune
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.
21
+ require 'net/ftp'
22
+ require 'yaml'
23
+ require 'digest/md5'
24
+ require 'tmpdir'
25
+ require 'logger'
26
+ require 'rubygems'
27
+ require 'mime/types'
28
+
29
+ module Net
30
+ class FTP
31
+ #
32
+ # Net::FTP extension
33
+ #
34
+ # Check if the +file+ exist on the remote FTP server
35
+ #
36
+ def remote_file_exist?( file )
37
+ ls( file ).size != 0
38
+ end
39
+
40
+ #
41
+ # Net::FTP extension
42
+ #
43
+ # Check if the +dir+ exist on the remote FTP server
44
+ #
45
+ def remote_dir_exist?( dir )
46
+ path = dir.split( "/" )
47
+ find = path.pop
48
+ path = path.join( "/" )
49
+ path = "." if path == ""
50
+
51
+ nlst( path ).include?( dir )
52
+ end
53
+
54
+ #
55
+ # Net::FTP extension
56
+ #
57
+ # Like FileUtils.mkdir_p but for Net::FTP
58
+ #
59
+ def mkdir_p( dir )
60
+ path = dir.split( "/" )
61
+ mkpath = path.shift
62
+ begin
63
+ mkdir( mkpath ) unless mkpath == ""
64
+ rescue Net::FTPPermError => e
65
+ raise Net::FTPPermError, e.message, caller unless remote_dir_exist?(mkpath)
66
+ end
67
+ path.each do |d|
68
+ mkpath = [mkpath, d].join( "/" )
69
+ begin
70
+ mkdir( mkpath )
71
+ rescue Net::FTPPermError => e
72
+ raise Net::FTPPermError, e.message, caller unless remote_dir_exist?(mkpath)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ class File
80
+ #
81
+ # File extension
82
+ #
83
+ # Check if the +file+ is a binary file
84
+ #
85
+ def self.binary?( file )
86
+ MIME::Types.type_for( file ).map{ |e| (e.binary?) ? e : nil }.compact.size > 0
87
+ end
88
+ end
89
+
90
+ class SyncFTP
91
+ attr_reader :host, :port, :username, :password
92
+
93
+ #
94
+ # Create a new SyncFTP object for +host+
95
+ #
96
+ # you can specify :
97
+ #
98
+ # * +:username+ - default = "anonymous"
99
+ # * +:password+ - default = nil
100
+ # * +:port+ - default = 21
101
+ # * +:logfile+ - default = STDOUT
102
+ # * +:loglevel+ - default = Logger::UNKNOWN (Cool if you don't want logs)
103
+ #
104
+ def initialize(host, options = {})
105
+ options = {:username => "anonymous", :password => nil, :logfile => STDOUT, :loglevel => Logger::UNKNOWN}.merge(options)
106
+ @host, @port = host, options[:port]||21
107
+ @username, @password = options[:username], options[:password]
108
+ @remote_md5s = {}
109
+ @local_md5s = {}
110
+ @log = Logger.new( options[:logfile] )
111
+ @log.level = options[:loglevel]
112
+ end
113
+
114
+ #
115
+ # Sync local to remote
116
+ #
117
+ # you can specify :
118
+ #
119
+ # * +:local+ : the local directory (default = ".")
120
+ # * +:remote+ : the remote directory (default = ".")
121
+ #
122
+ def sync( options = {} )
123
+ options = { :local => ".", :remote => "." }.merge( options )
124
+ local, remote = options[:local], options[:remote]
125
+
126
+ tmpname = tmpfilename
127
+ connect do |ftp|
128
+ # Read remote .syncftp
129
+ begin
130
+ ftp.gettextfile( remote+"/"+".syncftp", tmpname )
131
+ @remote_md5s = YAML.load( File.open( tmpname ).read )
132
+ rescue Net::FTPPermError => e
133
+ raise Net::FTPPermError, e.message, caller if ftp.remote_file_exist?( remote+"/"+".syncftp" )
134
+ end
135
+
136
+ # Do the job Bob !
137
+ send_dir( ftp, local, remote )
138
+
139
+ # Write new .syncftp
140
+ File.open( tmpname, 'w' ) do |out|
141
+ YAML.dump( @local_md5s, out )
142
+ end
143
+ ftp.puttextfile( tmpname, remote+"/"+".syncftp" )
144
+ end
145
+ File.delete( tmpname )
146
+ end
147
+
148
+ private
149
+ def tmpfilename #:nodoc:
150
+ tmpdir = Dir::tmpdir
151
+ basename = File.basename( $0 )
152
+ tempname = nil
153
+ n = 0
154
+
155
+ if $SAFE > 0 and tmpdir.tainted?
156
+ tmpdir = '/tmp'
157
+ end
158
+
159
+ begin
160
+ t = Time.now.strftime("%Y%m%d")
161
+ tmpfile = "#{basename}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}-#{n}"
162
+ n += 1
163
+ tmpname = File.join(tmpdir, tmpfile)
164
+ end while File.exist?(tmpname)
165
+
166
+ return tmpname
167
+ end
168
+
169
+ def connect #:nodoc:
170
+ ftp = Net::FTP.new( )
171
+ ftp.connect( @host, @port )
172
+ ftp.login( @username, @password )
173
+ yield ftp
174
+ ftp.close
175
+ end
176
+
177
+ def send_dir(ftp, local, remote) #:nodoc:
178
+ ftp.mkdir_p(remote)
179
+
180
+ Dir.foreach(local) do |file|
181
+ next if file == "." or file == ".."
182
+
183
+ local_file = File.join( local, file )
184
+ remote_file = remote + "/" + file
185
+
186
+ if File.stat(local_file).directory?
187
+ # It is a directory, we recursively send it
188
+ send_dir(ftp, local_file, remote_file)
189
+ else
190
+ @local_md5s[remote_file] = Digest::MD5.hexdigest( File.open(local_file).read )
191
+
192
+ if( @local_md5s[remote_file] != @remote_md5s[remote_file] )
193
+ @log.info "Copy #{local_file} to ftp://#{@host}:#{@port}/#{remote_file}"
194
+
195
+ # It's a file, we just send it
196
+ if File.binary?(local_file)
197
+ ftp.putbinaryfile(local_file, remote_file)
198
+ else
199
+ ftp.puttextfile(local_file, remote_file)
200
+ end
201
+ else
202
+ @log.info "#{local_file} don't need to be overwritten !"
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
data/syncftp.gemspec ADDED
@@ -0,0 +1,61 @@
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{syncftp}
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Gregoire Lejeune"]
12
+ s.date = %q{2010-04-15}
13
+ s.description = %q{Sync via FTP, only modified files}
14
+ s.email = %q{gregoire.lejeune@free.fr}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "lib/syncftp.rb",
27
+ "syncftp.gemspec",
28
+ "test/helper.rb",
29
+ "test/test_syncftp.rb"
30
+ ]
31
+ s.homepage = %q{http://github.com/glejeune/syncftp}
32
+ s.rdoc_options = ["--charset=UTF-8"]
33
+ s.require_paths = ["lib"]
34
+ s.rubygems_version = %q{1.3.6}
35
+ s.summary = %q{Sync via FTP, only modified files}
36
+ s.test_files = [
37
+ "test/helper.rb",
38
+ "test/test_syncftp.rb",
39
+ "examples/sample01.rb"
40
+ ]
41
+
42
+ if s.respond_to? :specification_version then
43
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
44
+ s.specification_version = 3
45
+
46
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
47
+ s.add_runtime_dependency(%q<mime-types>, [">= 0"])
48
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
49
+ s.add_development_dependency(%q<fakefs>, [">= 0"])
50
+ else
51
+ s.add_dependency(%q<mime-types>, [">= 0"])
52
+ s.add_dependency(%q<shoulda>, [">= 0"])
53
+ s.add_dependency(%q<fakefs>, [">= 0"])
54
+ end
55
+ else
56
+ s.add_dependency(%q<mime-types>, [">= 0"])
57
+ s.add_dependency(%q<shoulda>, [">= 0"])
58
+ s.add_dependency(%q<fakefs>, [">= 0"])
59
+ end
60
+ end
61
+
data/test/helper.rb ADDED
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'fakefs/safe'
5
+
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+ require 'syncftp'
9
+
10
+ class Test::Unit::TestCase
11
+ end
@@ -0,0 +1,13 @@
1
+ require 'helper'
2
+
3
+ class TestSyncftp < Test::Unit::TestCase
4
+ context "A User story" do
5
+ setup do
6
+ @sync = SyncFTP.new( 'localhost', :username => 'greg', :password => 'mcag71139' )
7
+ end
8
+
9
+ should "be initialized" do
10
+ assert_not_nil @sync
11
+ end
12
+ end
13
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: syncftp
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Gregoire Lejeune
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-04-15 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: mime-types
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: shoulda
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 0
41
+ version: "0"
42
+ type: :development
43
+ version_requirements: *id002
44
+ - !ruby/object:Gem::Dependency
45
+ name: fakefs
46
+ prerelease: false
47
+ requirement: &id003 !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ segments:
52
+ - 0
53
+ version: "0"
54
+ type: :development
55
+ version_requirements: *id003
56
+ description: Sync via FTP, only modified files
57
+ email: gregoire.lejeune@free.fr
58
+ executables: []
59
+
60
+ extensions: []
61
+
62
+ extra_rdoc_files:
63
+ - LICENSE
64
+ - README.rdoc
65
+ files:
66
+ - .document
67
+ - .gitignore
68
+ - LICENSE
69
+ - README.rdoc
70
+ - Rakefile
71
+ - VERSION
72
+ - lib/syncftp.rb
73
+ - syncftp.gemspec
74
+ - test/helper.rb
75
+ - test/test_syncftp.rb
76
+ has_rdoc: true
77
+ homepage: http://github.com/glejeune/syncftp
78
+ licenses: []
79
+
80
+ post_install_message:
81
+ rdoc_options:
82
+ - --charset=UTF-8
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ segments:
90
+ - 0
91
+ version: "0"
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ segments:
97
+ - 0
98
+ version: "0"
99
+ requirements: []
100
+
101
+ rubyforge_project:
102
+ rubygems_version: 1.3.6
103
+ signing_key:
104
+ specification_version: 3
105
+ summary: Sync via FTP, only modified files
106
+ test_files:
107
+ - test/helper.rb
108
+ - test/test_syncftp.rb
109
+ - examples/sample01.rb