libelule 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ tmp
data/CHANGELOG ADDED
@@ -0,0 +1,5 @@
1
+ # CHANGELOG
2
+
3
+ ## v0.0.1
4
+
5
+ * First release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in libelule.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ Copyright (c) 2011 Patrick Huesler
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ MOST OF ALL: USE IT FOR GOOD, NOT EVIL
data/README ADDED
@@ -0,0 +1,5 @@
1
+ require 'yaml'
2
+
3
+ libelule = FtpSynbc::Client.new('hostname', 'username','password')
4
+ libelule.one_way_sync_from_local_to_remote('target_dir','base dir')
5
+
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ task :default => ['test:units']
6
+
7
+ namespace :test do
8
+ Rake::TestTask.new(:units) do |t|
9
+ t.libs << "test"
10
+ t.test_files = FileList['test/*_test.rb']
11
+
12
+ t.verbose = true
13
+ end
14
+ end
data/lib/libelule.rb ADDED
@@ -0,0 +1,186 @@
1
+ # encoding: UTF-8
2
+ require "libelule/version"
3
+ require 'net/ftp'
4
+ module Libelule
5
+ class Client
6
+ class << self
7
+ attr_accessor :silence_log_messages
8
+ silence_log_messages = true
9
+ end
10
+
11
+ attr_accessor :ftp_client
12
+
13
+ def initialize(host, user, password)
14
+ @host = host
15
+ @user = user
16
+ @password = password
17
+ @ftp_client = Net::FTP
18
+ end
19
+
20
+ def ftp
21
+ if @ftp.nil? || @ftp.closed?
22
+ @ftp = ftp_client.send(:new, @host)
23
+ @ftp.passive = true
24
+ end
25
+ @ftp
26
+ end
27
+
28
+ def one_way_sync_from_local_to_remote(local_dir, remote_dir)
29
+ begin
30
+ ftp.login(@user, @password)
31
+ log "logged in, start syncing..."
32
+ sync_folder(local_dir, remote_dir)
33
+ log "sync finished"
34
+ return true
35
+ rescue Net::FTPPermError => e
36
+ log "Failed: #{e.message}"
37
+ return false
38
+ end
39
+ end
40
+
41
+ def clean_up(target_dir, options)
42
+ suffix_to_keep = options[:suffix_to_keep]
43
+ dry_run = options[:dry_run]
44
+ raise ArgumentError, 'please provide a suffix to keep' unless /[A-Z]/ =~ suffix_to_keep
45
+ ftp.login(@user, @password)
46
+ clean_up_folder(target_dir, suffix_to_keep, dry_run)
47
+ end
48
+
49
+ def clean_up_with_manifest(target_dir, manifest, options = {})
50
+ ftp.login(@user, @password)
51
+ dry_run = options[:dry_run]
52
+ clean_up_folder_with_manifest(target_dir, manifest, dry_run)
53
+ end
54
+
55
+ private
56
+
57
+ def clean_up_folder_with_manifest(target_dir, manifest, dry_run = false)
58
+ ftp.chdir(target_dir)
59
+ remote_dirs, remote_files = get_remote_dir_and_file_names
60
+ remote_files.each do |file|
61
+ full_path = File.join(ftp.pwd, file)
62
+ if manifest.include?(full_path)
63
+ log "[KEEP] file #{full_path}"
64
+ else
65
+ ftp.delete(file) unless dry_run
66
+ log "[DELETE] file #{full_path}"
67
+ end
68
+ end
69
+ remote_dirs.each do |dir|
70
+ clean_up_folder_with_manifest(dir, manifest, dry_run)
71
+ end
72
+ ftp.chdir("..")
73
+ end
74
+
75
+ def clean_up_folder(target_dir, suffix_to_keep, dry_run = false)
76
+ ftp.chdir(target_dir)
77
+ remote_dirs, remote_files = get_remote_dir_and_file_names
78
+ remote_files.each do |file|
79
+ if file =~ /\.[0-9]{1,5}[^#{suffix_to_keep}]?\./
80
+ log "deleting file #{target_dir}/#{file}"
81
+ ftp.delete(file) unless dry_run
82
+ end
83
+ end
84
+ remote_dirs.each do |dir|
85
+ clean_up_folder(dir, suffix_to_keep, dry_run)
86
+ end
87
+ ftp.chdir("..")
88
+ end
89
+
90
+ def put_title(title)
91
+ log "#{'-'*80}\n#{title}:\n\n"
92
+ end
93
+
94
+ def full_file_path(file)
95
+ File.join(Dir.pwd, file)
96
+ end
97
+
98
+ def upload_file(file)
99
+ put_title "upload file: #{full_file_path(file)}"
100
+ ftp.put(file)
101
+ end
102
+
103
+ def upload_folder(dir)
104
+ put_title "upload folder: #{full_file_path(dir)}"
105
+ Dir.chdir dir
106
+ ftp.mkdir dir
107
+ ftp.chdir dir
108
+
109
+ local_dirs, local_files = get_local_dir_and_file_names
110
+
111
+ local_dirs.each do |subdir|
112
+ upload_folder(subdir)
113
+ end
114
+
115
+ local_files.each do |file|
116
+ upload_file(file)
117
+ end
118
+
119
+ Dir.chdir("..")
120
+ ftp.chdir("..")
121
+ end
122
+
123
+ def sync_folder(local_dir, remote_dir)
124
+ Dir.chdir local_dir
125
+ ftp.chdir remote_dir
126
+
127
+ put_title "process folder: #{Dir.pwd}"
128
+
129
+ local_dirs, local_files = get_local_dir_and_file_names
130
+ remote_dirs, remote_files = get_remote_dir_and_file_names
131
+
132
+ new_dirs = local_dirs - remote_dirs
133
+ new_files = local_files - remote_files
134
+ existing_dirs = local_dirs - new_dirs
135
+ existing_files = local_files - new_files
136
+
137
+ new_files.each do |file|
138
+ upload_file(file)
139
+ end
140
+
141
+ new_dirs.each do |dir|
142
+ upload_folder(dir)
143
+ end
144
+
145
+ existing_dirs.each do |dir|
146
+ sync_folder(dir, dir)
147
+ end
148
+
149
+ Dir.chdir("..")
150
+ ftp.chdir("..")
151
+ end
152
+
153
+ def get_local_dir_and_file_names
154
+ dirs = []
155
+ files = []
156
+ Dir.glob("*").each do |file|
157
+ if File.file?(file)
158
+ files << file
159
+ else
160
+ dirs << file
161
+ end
162
+ end
163
+ return [dirs, files]
164
+ end
165
+
166
+ def get_remote_dir_and_file_names
167
+ dirs = []
168
+ files = []
169
+ ftp.ls do |file|
170
+ #-rw-r--r-- 1 james staff 6 Jan 07 03:54 hello.txt
171
+ file_name = file.gsub(/\S+\s+\d+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+/, '')
172
+ case file[0, 1]
173
+ when "-"
174
+ files << file_name
175
+ when "d"
176
+ dirs << file_name
177
+ end
178
+ end
179
+ return [dirs, files]
180
+ end
181
+
182
+ def log(message)
183
+ puts message unless self.class.silence_log_messages
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,3 @@
1
+ module Libelule
2
+ VERSION = "0.0.1"
3
+ end
data/libelule.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "libelule/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "libelule"
7
+ s.version = Libelule::VERSION
8
+ s.authors = ["Patrick Huesler"]
9
+ s.email = ["patrick.huesler@gmail.com"]
10
+ s.homepage = "https://github.com/phuesler/ftpsync"
11
+ s.summary = "Syncing files over ftp/sftp"
12
+ s.description = ""
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.require_paths = ["lib"]
17
+ s.add_development_dependency "rake"
18
+ s.add_development_dependency "shoulda-context"
19
+ s.add_development_dependency "mocha"
20
+ end
@@ -0,0 +1,187 @@
1
+ # encoding: UTF-8
2
+ require 'test_helper'
3
+
4
+ class Libelule::ClientTest < Test::Unit::TestCase
5
+
6
+ FTP_SOURCE_DIR = File.join("/tmp","libelule_test")
7
+ FTP_TARGET_DIR = "remote_assets"
8
+
9
+ def ftp_string_for_filename(file_name)
10
+ "-rw-r--r-- 1 james staff 6 Jan 07 03:54 #{file_name}"
11
+ end
12
+
13
+ def ftp_string_for_directory(directory_name)
14
+ "drw-r--r-- 1 james staff 6 Jan 07 03:54 #{directory_name}"
15
+ end
16
+
17
+ Libelule::Client.silence_log_messages = true
18
+
19
+ context "one_way_sync_from_local_to_remote" do
20
+ setup do
21
+ FileUtils.mkdir_p(FTP_SOURCE_DIR)
22
+
23
+ @ftp_client_test_double = stub_everything('fake_ftp_client')
24
+ @ftp_fake_client_class = stub("FakeFTPClient", :new => @ftp_client_test_double)
25
+
26
+ @ftp = Libelule::Client.new("ftp.test.host", "ftpuser", "ftppassword")
27
+ @ftp.ftp_client = @ftp_fake_client_class
28
+ end
29
+
30
+ teardown do
31
+ FileUtils.rm_rf(FTP_SOURCE_DIR)
32
+ FileUtils.rm_rf(FTP_TARGET_DIR)
33
+ end
34
+
35
+ should "open a connection to the host" do
36
+ @ftp_fake_client_class.expects(:new).with('ftp.test.host').returns(@ftp_client_test_double)
37
+ @ftp.one_way_sync_from_local_to_remote(FTP_SOURCE_DIR, FTP_TARGET_DIR)
38
+ end
39
+
40
+ should "login into the ftp server" do
41
+ @ftp_client_test_double.expects(:login).with('ftpuser','ftppassword')
42
+ @ftp.one_way_sync_from_local_to_remote(FTP_SOURCE_DIR, FTP_TARGET_DIR)
43
+ end
44
+
45
+ should 'copy new files to the remote host' do
46
+ FileUtils.touch(File.join(FTP_SOURCE_DIR, 'test.txt'))
47
+ @ftp_client_test_double.expects(:put).with("test.txt")
48
+ @ftp.one_way_sync_from_local_to_remote(FTP_SOURCE_DIR, FTP_TARGET_DIR)
49
+ end
50
+
51
+ should 'copy new directories' do
52
+ FileUtils.mkdir(File.join(FTP_SOURCE_DIR, 'testdirectory'))
53
+ @ftp_client_test_double.expects(:mkdir).with('testdirectory')
54
+ @ftp.one_way_sync_from_local_to_remote(FTP_SOURCE_DIR, FTP_TARGET_DIR)
55
+ end
56
+
57
+ should 'copy files of subdirectories' do
58
+ FileUtils.mkdir(File.join(FTP_SOURCE_DIR, 'testdirectory'))
59
+ FileUtils.touch File.join(FTP_SOURCE_DIR, 'testdirectory', 'subfile.txt')
60
+ @ftp_client_test_double.expects(:put).with('subfile.txt')
61
+ @ftp.one_way_sync_from_local_to_remote(FTP_SOURCE_DIR, FTP_TARGET_DIR)
62
+ end
63
+
64
+ should 'not copy a file to the remote host if it exists already' do
65
+ FileUtils.touch(File.join(FTP_SOURCE_DIR, 'test.txt'))
66
+ @ftp_client_test_double.stubs(:ls).yields(ftp_string_for_filename("test.txt"))
67
+ @ftp_client_test_double.expects(:put).with('test.txt').times(0)
68
+ @ftp.one_way_sync_from_local_to_remote(FTP_SOURCE_DIR, FTP_TARGET_DIR)
69
+ end
70
+ end
71
+
72
+ context 'clean up resources with a manifest' do
73
+ setup do
74
+ @ftp_client_test_double = stub_everything('fake_ftp_client')
75
+ @ftp_fake_client_class = stub("FakeFTPClient", :new => @ftp_client_test_double)
76
+
77
+ @ftp = Libelule::Client.new("ftp.test.host", "ftpuser", "ftppassword")
78
+ @ftp.ftp_client = @ftp_fake_client_class
79
+
80
+ @ftp_client_test_double.stubs(:ls).multiple_yields(
81
+ ftp_string_for_filename("fake.3219C.txt"),
82
+ ftp_string_for_filename("fake.3211C.txt"),
83
+ ftp_string_for_directory("subdir1")
84
+ ).then.multiple_yields(
85
+ ftp_string_for_filename("fake.2223C.txt"),
86
+ ftp_string_for_filename("fake.2222C.txt"),
87
+ ftp_string_for_directory("subdir2")
88
+ ).then.multiple_yields(
89
+ ftp_string_for_filename("fake.3333C.txt"),
90
+ ftp_string_for_filename("fake.3332C.txt")
91
+ )
92
+ @ftp_client_test_double.stubs(:pwd).returns('/published', '/published', '/published/subdir1', '/published/subdir1', '/published/subdir1/subdir2')
93
+ @manifest = ["/published/fake.3219C.txt", "/published/subdir1/fake.2223C.txt", "/published/subdir1/subdir2/fake.3333C.txt"]
94
+ end
95
+
96
+ should 'delete versions not in the manifest' do
97
+ @ftp_client_test_double.expects(:delete).with('fake.3211C.txt')
98
+ @ftp.clean_up_with_manifest(FTP_TARGET_DIR, @manifest)
99
+ end
100
+
101
+ should 'keep files in the manifest' do
102
+ @ftp_client_test_double.expects(:delete).with('fake.3219C.txt').never
103
+ @ftp.clean_up_with_manifest(FTP_TARGET_DIR, @manifest)
104
+ end
105
+
106
+ should 'keep files in subdirectories' do
107
+ @ftp_client_test_double.expects(:delete).with('fake.3219C.txt').never
108
+ @ftp_client_test_double.expects(:delete).with('fake.2223C.txt').never
109
+ @ftp_client_test_double.expects(:delete).with('fake.3333C.txt').never
110
+ @ftp.clean_up_with_manifest(FTP_TARGET_DIR, @manifest)
111
+ end
112
+
113
+ should 'descend into subdirectories and delete' do
114
+ @ftp_client_test_double.expects(:delete).with('fake.2222C.txt')
115
+ @ftp_client_test_double.expects(:delete).with('fake.3332C.txt')
116
+ @ftp.clean_up_with_manifest(FTP_TARGET_DIR, @manifest)
117
+ end
118
+
119
+ should 'have a dry run option' do
120
+ @ftp_client_test_double.expects(:delete).never
121
+ @ftp.clean_up_with_manifest(FTP_TARGET_DIR, @manifest, :dry_run => true)
122
+ end
123
+ end
124
+
125
+ context 'clean up resources by index' do
126
+ setup do
127
+ @ftp_client_test_double = stub_everything('fake_ftp_client')
128
+ @ftp_fake_client_class = stub("FakeFTPClient", :new => @ftp_client_test_double)
129
+
130
+ @ftp = Libelule::Client.new("ftp.test.host", "ftpuser", "ftppassword")
131
+ @ftp.ftp_client = @ftp_fake_client_class
132
+
133
+ @ftp_client_test_double.stubs(:ls).multiple_yields(
134
+ ftp_string_for_filename("flash.14695871.css"),
135
+ ftp_string_for_filename("512K"),
136
+ ftp_string_for_filename("test.txt"),
137
+ ftp_string_for_filename("test.1000.txt"),
138
+ ftp_string_for_filename("test.1001A.txt"),
139
+ ftp_string_for_filename("test.1002B.txt"),
140
+ ftp_string_for_filename("test.1003C.txt"),
141
+ ftp_string_for_filename("test.1004C.txt")
142
+ )
143
+ end
144
+
145
+ should 'delete old versions' do
146
+ @ftp_client_test_double.expects(:delete).with('test.1000.txt')
147
+ @ftp_client_test_double.expects(:delete).with('test.1001A.txt')
148
+ @ftp_client_test_double.expects(:delete).with('test.1002B.txt')
149
+ @ftp.clean_up(FTP_TARGET_DIR, :suffix_to_keep => 'C')
150
+ end
151
+
152
+ should 'keep unrevisioned files' do
153
+ @ftp_client_test_double.expects(:delete).with('test.txt').never
154
+ @ftp_client_test_double.expects(:delete).with('512K').never
155
+ @ftp.clean_up(FTP_TARGET_DIR, :suffix_to_keep => 'C')
156
+ end
157
+
158
+ should 'keep files with suffix C' do
159
+ @ftp_client_test_double.expects(:delete).with('test.1004C.txt').never
160
+ @ftp_client_test_double.expects(:delete).with('test.1003C.txt').never
161
+ @ftp.clean_up(FTP_TARGET_DIR, :suffix_to_keep => 'C')
162
+ end
163
+
164
+ should 'keep files with a different versioning schema' do
165
+ @ftp_client_test_double.expects(:delete).with('flash.14695871.css').never
166
+ @ftp.clean_up(FTP_TARGET_DIR, :suffix_to_keep => 'C')
167
+ end
168
+
169
+ should 'make the suffix to keep configurable' do
170
+ @ftp_client_test_double.expects(:delete).with('test.1001A.txt').never
171
+ @ftp.clean_up(FTP_TARGET_DIR, :suffix_to_keep => 'A')
172
+ end
173
+
174
+ should 'raise an exception if suffix to keep is blank' do
175
+ assert_raises(ArgumentError){ @ftp.clean_up(FTP_TARGET_DIR, :suffix_to_keep => nil) }
176
+ end
177
+
178
+ should 'raise an exception if suffix is not a single uppercase letter' do
179
+ assert_raises(ArgumentError){ @ftp.clean_up(FTP_TARGET_DIR, :suffix_to_keep => 'foo') }
180
+ end
181
+
182
+ should 'not delete files when the dry run option is set' do
183
+ @ftp_client_test_double.expects(:delete).never
184
+ @ftp.clean_up(FTP_TARGET_DIR, :suffix_to_keep => 'C', :dry_run => true)
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,7 @@
1
+ $LOAD_PATH.unshift( File.join( File.dirname(__FILE__), "..", "lib") )
2
+
3
+ require "rubygems"
4
+ require 'test/unit'
5
+ require 'mocha'
6
+ require 'shoulda-context'
7
+ require 'libelule'
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: libelule
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Patrick Huesler
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-09-28 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rake
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :development
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: shoulda-context
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :development
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: mocha
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ type: :development
61
+ version_requirements: *id003
62
+ description: ""
63
+ email:
64
+ - patrick.huesler@gmail.com
65
+ executables: []
66
+
67
+ extensions: []
68
+
69
+ extra_rdoc_files: []
70
+
71
+ files:
72
+ - .gitignore
73
+ - CHANGELOG
74
+ - Gemfile
75
+ - LICENSE
76
+ - README
77
+ - Rakefile
78
+ - lib/libelule.rb
79
+ - lib/libelule/version.rb
80
+ - libelule.gemspec
81
+ - test/libelule_test.rb
82
+ - test/test_helper.rb
83
+ homepage: https://github.com/phuesler/ftpsync
84
+ licenses: []
85
+
86
+ post_install_message:
87
+ rdoc_options: []
88
+
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ hash: 3
97
+ segments:
98
+ - 0
99
+ version: "0"
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ hash: 3
106
+ segments:
107
+ - 0
108
+ version: "0"
109
+ requirements: []
110
+
111
+ rubyforge_project:
112
+ rubygems_version: 1.8.6
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: Syncing files over ftp/sftp
116
+ test_files:
117
+ - test/libelule_test.rb
118
+ - test/test_helper.rb