libelule 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/.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