Piggy 0.4.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +91 -0
- data/INSTALL.txt +77 -0
- data/LICENCE.txt +339 -0
- data/README.txt +27 -0
- data/UNINSTALL.txt +24 -0
- data/bin/directory_diff +6 -0
- data/bin/ftp_browser +6 -0
- data/bin/piggy +6 -0
- data/lib/directory_diff.rb +35 -0
- data/lib/ftp_browser.rb +33 -0
- data/lib/icons/auto-select-for-del.ico +0 -0
- data/lib/icons/auto-select-right.ico +0 -0
- data/lib/icons/auto-upload-right.ico +0 -0
- data/lib/icons/back.ico +0 -0
- data/lib/icons/checked.ico +0 -0
- data/lib/icons/copy.png +0 -0
- data/lib/icons/cut.png +0 -0
- data/lib/icons/dir.ico +0 -0
- data/lib/icons/dirup.ico +0 -0
- data/lib/icons/file.ico +0 -0
- data/lib/icons/fileopen.png +0 -0
- data/lib/icons/filesave.ico +0 -0
- data/lib/icons/gallery.ico +0 -0
- data/lib/icons/generate.ico +0 -0
- data/lib/icons/minus.png +0 -0
- data/lib/icons/move-right-dir.ico +0 -0
- data/lib/icons/move-right-file.ico +0 -0
- data/lib/icons/newer-file.ico +0 -0
- data/lib/icons/next-image.ico +0 -0
- data/lib/icons/paste.png +0 -0
- data/lib/icons/plus.png +0 -0
- data/lib/icons/preview.png +0 -0
- data/lib/icons/previous-image.ico +0 -0
- data/lib/icons/reload.ico +0 -0
- data/lib/icons/remove-selected.ico +0 -0
- data/lib/icons/rotate.ico +0 -0
- data/lib/icons/slides.ico +0 -0
- data/lib/icons/stop-slides.ico +0 -0
- data/lib/icons/unchecked-dir.ico +0 -0
- data/lib/icons/unchecked.ico +0 -0
- data/lib/icons/upload-selected.ico +0 -0
- data/lib/icons/upload.ico +0 -0
- data/lib/piggy-core/alive_check.rb +44 -0
- data/lib/piggy-core/debug.rb +21 -0
- data/lib/piggy-core/encoding.rb +11 -0
- data/lib/piggy-core/environment.rb +66 -0
- data/lib/piggy-core/exifr_adapter.rb +85 -0
- data/lib/piggy-core/file_info.rb +218 -0
- data/lib/piggy-core/ftp_adapter.rb +106 -0
- data/lib/piggy-core/htmlgen.rb +242 -0
- data/lib/piggy-core/nconvert_thumbsgen.rb +73 -0
- data/lib/piggy-core/options.rb +116 -0
- data/lib/piggy-core/options_persistence.rb +54 -0
- data/lib/piggy-core/progress.rb +48 -0
- data/lib/piggy-core/rmagick_thumbnail_page_generator.rb +30 -0
- data/lib/piggy-core/thumbnail_generator.rb +79 -0
- data/lib/piggy-core/thumbnail_page_generator.rb +542 -0
- data/lib/piggy-core/upload_info.rb +41 -0
- data/lib/piggy-core/version.rb +18 -0
- data/lib/piggy-core/winshell.rb +240 -0
- data/lib/piggy-gui/directory_diff_widget.rb +398 -0
- data/lib/piggy-gui/filtered_file_list.rb +243 -0
- data/lib/piggy-gui/fox_thumbsgen.rb +18 -0
- data/lib/piggy-gui/ftp_browser_widget.rb +395 -0
- data/lib/piggy-gui/html_generation_dialog.rb +157 -0
- data/lib/piggy-gui/image_processor.rb +140 -0
- data/lib/piggy-gui/multiimagecanvas.rb +163 -0
- data/lib/piggy-gui/options_dialog.rb +85 -0
- data/lib/piggy-gui/piggy_image_browser.rb +776 -0
- data/lib/piggy-gui/pipe_log.rb +67 -0
- data/lib/piggy-gui/progress_with_dialog.rb +51 -0
- data/lib/piggy-gui/require-fox.rb +87 -0
- data/lib/piggy.rb +35 -0
- data/lib/templates/fuss.htm +10 -0
- data/lib/templates/kopf.htm +13 -0
- data/lib/templates/navigation.htm +11 -0
- data/lib/templates/slideshow.htm +129 -0
- data/lib/templates/slideshow.js +691 -0
- data/lib/templates/styles/basic/style.css +27 -0
- data/lib/templates/styles/black/style.css +64 -0
- data/lib/templates/styles/roundedbox/roundedbox_lo.gif +0 -0
- data/lib/templates/styles/roundedbox/roundedbox_lu.gif +0 -0
- data/lib/templates/styles/roundedbox/roundedbox_ro.gif +0 -0
- data/lib/templates/styles/roundedbox/roundedbox_ru.gif +0 -0
- data/lib/templates/styles/roundedbox/style.css +70 -0
- data/lib/templates/styles/shadow/lo.gif +0 -0
- data/lib/templates/styles/shadow/lu.gif +0 -0
- data/lib/templates/styles/shadow/ro.gif +0 -0
- data/lib/templates/styles/shadow/ru.gif +0 -0
- data/lib/templates/styles/shadow/style.css +63 -0
- data/test/file_info_test.rb +117 -0
- data/test.rb +8 -0
- data/web/IMAGE_PROCESSING.txt +53 -0
- data/web/INSTALL.txt +74 -0
- data/web/extern.gif +0 -0
- data/web/ftp-browser.png +0 -0
- data/web/index-de.html +60 -0
- data/web/index.html +57 -0
- data/web/piggy.png +0 -0
- data/web/style.css +14 -0
- metadata +177 -0
Binary file
|
Binary file
|
Binary file
|
data/lib/icons/minus.png
ADDED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/lib/icons/paste.png
ADDED
Binary file
|
data/lib/icons/plus.png
ADDED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
# This class works similar to a timeout.
|
6
|
+
# Run it like this:
|
7
|
+
#
|
8
|
+
# x = AliveCheck(10)
|
9
|
+
# begin
|
10
|
+
# x.check { ... x.alive! ... }
|
11
|
+
# rescue Timeout::Error => msg
|
12
|
+
# ...
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# If x doesn't get an alive! notification for
|
16
|
+
# 10 to 20 seconds, it will raise a Timeout::Error
|
17
|
+
class AliveCheck
|
18
|
+
def initialize(check_interval_sec, ex_msg = 'execution expired')
|
19
|
+
@sec = check_interval_sec
|
20
|
+
@alive = false
|
21
|
+
@ex_msg = ex_msg
|
22
|
+
end
|
23
|
+
|
24
|
+
def check
|
25
|
+
checked_thread = Thread.current
|
26
|
+
@alive = true
|
27
|
+
aliveChecker = Thread.new {
|
28
|
+
while(@alive) do
|
29
|
+
@alive = false
|
30
|
+
sleep(@sec)
|
31
|
+
end
|
32
|
+
checked_thread.raise(Timeout::Error, @ex_msg) if checked_thread.alive?
|
33
|
+
}
|
34
|
+
begin
|
35
|
+
yield
|
36
|
+
ensure
|
37
|
+
aliveChecker.kill if aliveChecker and aliveChecker.alive?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def alive!
|
42
|
+
@alive = true
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Global methods temporaryly used for debugging.
|
4
|
+
# Inspired by the book
|
5
|
+
# "Programmieren mit Ruby" by R�hrl/Schmiedl/Weyss"
|
6
|
+
|
7
|
+
def beginTrace
|
8
|
+
$stderr.puts "Begin Trace in: #{caller.first}"
|
9
|
+
set_trace_func proc {
|
10
|
+
|event, file, line, id, binding, classname|
|
11
|
+
unless File.basename(file) == 'debug.rb'
|
12
|
+
$stderr.printf("%8s %s-, %-2d %10s %8s\n",
|
13
|
+
event, file, line, id, classname)
|
14
|
+
end
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def endTrace
|
19
|
+
$stderr.set_trace_func(nil)
|
20
|
+
puts "End Trace in: #{caller.first}"
|
21
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Require this file will set global constants to appropriate values:
|
4
|
+
#
|
5
|
+
# - PIGGY_PATH points to piggy's lib directory.
|
6
|
+
# - RMAGICK_AVAILIABLE will be true if RMagick is installed
|
7
|
+
#
|
8
|
+
# The standard method is to find the installed gem and let
|
9
|
+
# PIGGY_PATH point to the subdirectory lib.
|
10
|
+
#
|
11
|
+
# If the gem isn't found, we assume that piggy has been started
|
12
|
+
# from within the lib directory itself and therefore set
|
13
|
+
# PIGGY_PATH = '.'. (This is often the case during development.)
|
14
|
+
#
|
15
|
+
# Note that PIGGY_PATH uses the Ruby notation of the path.
|
16
|
+
#
|
17
|
+
# So, what ist this good for? We'll find all resource files,
|
18
|
+
# icons or other user-independent configuration files using
|
19
|
+
# paths relative to PIGGY_PATH.
|
20
|
+
|
21
|
+
begin
|
22
|
+
require 'rubygems'
|
23
|
+
require 'piggy-core/version.rb'
|
24
|
+
|
25
|
+
# Try to find lib directory of this specific Piggy gem version
|
26
|
+
def findPiggyDirectory
|
27
|
+
puts "Checking $LOAD_PATH"
|
28
|
+
$LOAD_PATH.each do
|
29
|
+
|d|
|
30
|
+
if /piggy.lib$/ =~ d
|
31
|
+
return d.gsub(/\\/, '/')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
puts "Checking Gems"
|
35
|
+
piggyGemDir = "Piggy-#{PiggyVersion.versionString.gsub(/-/, '.')}"
|
36
|
+
all = Gem::path.collect { |gempath|
|
37
|
+
File.join(gempath, 'gems', piggyGemDir)
|
38
|
+
}.select { |f|
|
39
|
+
File.directory?(f)
|
40
|
+
}
|
41
|
+
return all.empty? ? '.' : File.join(all.first, 'lib')
|
42
|
+
end
|
43
|
+
PIGGY_PATH = findPiggyDirectory
|
44
|
+
puts "Successfully detecting piggy gem lib in #{PIGGY_PATH}"
|
45
|
+
rescue LoadError
|
46
|
+
PIGGY_PATH = '.'
|
47
|
+
end
|
48
|
+
|
49
|
+
# Temporary change working dir while executing a given block
|
50
|
+
# (depriciated)
|
51
|
+
def inPath(path, &block)
|
52
|
+
workingDirSik = Dir.getwd
|
53
|
+
begin
|
54
|
+
Dir.chdir(path)
|
55
|
+
block.call
|
56
|
+
ensure
|
57
|
+
Dir.chdir(workingDirSik)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
begin
|
62
|
+
require 'RMagick'
|
63
|
+
RMAGICK_AVAILIABLE = true
|
64
|
+
rescue LoadError
|
65
|
+
RMAGICK_AVAILIABLE = false
|
66
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#$Id: options.rb 177 2007-11-21 21:59:52Z Sascha $
|
3
|
+
|
4
|
+
require 'exifr'
|
5
|
+
|
6
|
+
# This adapter is used as an additional
|
7
|
+
# layer between Piggy and EXIFR.
|
8
|
+
class ExifrAdapter
|
9
|
+
def initialize(file)
|
10
|
+
@exif = loadExif(file)
|
11
|
+
end
|
12
|
+
|
13
|
+
def exif?
|
14
|
+
@exif && @exif.exif?
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
return 'no exif' unless exif?
|
19
|
+
desc = "Exif:\n\t"
|
20
|
+
h = @exif.exif.to_hash
|
21
|
+
desc += h.keys.collect { |k| "#{k.to_s}: #{h[k].to_s}" }.join("\n\t")
|
22
|
+
return desc
|
23
|
+
end
|
24
|
+
|
25
|
+
def viewDegrees
|
26
|
+
return 0 unless exif?
|
27
|
+
orientation = @exif.exif[:orientation]
|
28
|
+
return 0 if orientation.nil?
|
29
|
+
imageMock = ExifrDegreeComuter.new
|
30
|
+
orientation.transform_rmagick(imageMock)
|
31
|
+
return imageMock.degree
|
32
|
+
end
|
33
|
+
|
34
|
+
def width
|
35
|
+
exif? ? @exif.width : 0
|
36
|
+
end
|
37
|
+
|
38
|
+
def height
|
39
|
+
exif? ? @exif.height : 0
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def loadExif(file)
|
45
|
+
if(hasExtension?(file, 'jpg'))
|
46
|
+
EXIFR::JPEG.new(file)
|
47
|
+
elsif(hasExtension?(file, 'tif'))
|
48
|
+
EXIFR::TIFF.new(file)
|
49
|
+
else
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def hasExtension?(filename, ext)
|
55
|
+
File.basename(filename.upcase, ext.upcase) !=
|
56
|
+
File.basename(filename.upcase)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Simulate rmagick image to compte degree instead
|
61
|
+
# of rotate.
|
62
|
+
class ExifrDegreeComuter
|
63
|
+
attr_reader(:degree)
|
64
|
+
|
65
|
+
def initialize
|
66
|
+
@degree = 0
|
67
|
+
end
|
68
|
+
|
69
|
+
def flop
|
70
|
+
end
|
71
|
+
|
72
|
+
def flip
|
73
|
+
end
|
74
|
+
|
75
|
+
def rotate(degree)
|
76
|
+
@degree = degree
|
77
|
+
self
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class EXIFR::TIFF::Orientation
|
82
|
+
def to_s
|
83
|
+
@value.to_s + ' (' + @type.to_s + ')'
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
#$Id: file_info.rb 195 2008-05-29 21:32:40Z Sascha $
|
3
|
+
|
4
|
+
# Provides classes to hold some information for single files,
|
5
|
+
# directory contents and multiple files at different locations.
|
6
|
+
|
7
|
+
# Some utility methods for path handling.
|
8
|
+
module FilePath
|
9
|
+
|
10
|
+
# Ruby path notation for a given operation system/file system
|
11
|
+
# specific path
|
12
|
+
def FilePath.internPath(pathString)
|
13
|
+
return '' unless pathString
|
14
|
+
newPath = pathString.gsub(/\\/, File::SEPARATOR)
|
15
|
+
return newPath =~ /:$/ ? newPath + File::SEPARATOR : newPath
|
16
|
+
end
|
17
|
+
|
18
|
+
# Just as File.join but handles at least one possible error.
|
19
|
+
#
|
20
|
+
# ===Example
|
21
|
+
#
|
22
|
+
# FilePath.join('bla/', '/blubb') # => 'bla/blubb'
|
23
|
+
def FilePath.join(string1, string2)
|
24
|
+
return string2 unless string1 && !string1.empty?
|
25
|
+
return string1 unless string2 && !string2.empty?
|
26
|
+
noLeadingSeps2 = string2.sub(Regexp.compile("#{File::SEPARATOR}*"), '')
|
27
|
+
return File.join(string1, noLeadingSeps2)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Sections of a given pathString
|
31
|
+
def FilePath.split(pathString)
|
32
|
+
pathString.split(File::SEPARATOR)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Removes the last section of a given pathString.
|
36
|
+
# Won't remove a section of a file system root.
|
37
|
+
# Possible roots are '/', 'C:', 'X:/', ...
|
38
|
+
def FilePath.oneDirUp(pathString)
|
39
|
+
return '' unless pathString
|
40
|
+
sections = FilePath.split(pathString)
|
41
|
+
return '' unless sections || !sections.empty?
|
42
|
+
if sections.size == 1
|
43
|
+
return pathString if pathString =~ /:/
|
44
|
+
return pathString if pathString == '/'
|
45
|
+
return ''
|
46
|
+
end
|
47
|
+
return '' unless sections[-1]
|
48
|
+
numOfCharsToStrip = sections[-1].size + 2
|
49
|
+
return FilePath.internPath(pathString[0..-numOfCharsToStrip])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Every String could be a Filename but we want
|
54
|
+
# a class containing name and path to make some things
|
55
|
+
# more explicit.
|
56
|
+
class FileName
|
57
|
+
attr_accessor(:name)
|
58
|
+
attr_reader(:path)
|
59
|
+
|
60
|
+
def initialize(theName, thePath = '')
|
61
|
+
@name = theName
|
62
|
+
setPath(thePath)
|
63
|
+
end
|
64
|
+
|
65
|
+
def path=(newPath)
|
66
|
+
setPath(newPath)
|
67
|
+
end
|
68
|
+
|
69
|
+
def setPath(newPath)
|
70
|
+
@path = FilePath.internPath(newPath)
|
71
|
+
end
|
72
|
+
|
73
|
+
def nameWithPath
|
74
|
+
return FilePath.join(path, name)
|
75
|
+
end
|
76
|
+
|
77
|
+
def nameWithoutExtension
|
78
|
+
return File.basename(name, '.' + extension)
|
79
|
+
end
|
80
|
+
|
81
|
+
def extension
|
82
|
+
return name.split('.')[-1]
|
83
|
+
end
|
84
|
+
|
85
|
+
def pathSections
|
86
|
+
return FilePath.split(path)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# An entity object where you can set all attributes no matter
|
91
|
+
# if (or where) the file exists. Next to a name and path it
|
92
|
+
# has further attributes such as a creation and a modification
|
93
|
+
# time and a size in bytes. New instances aren't directories by
|
94
|
+
# default but you can set this flag, too.
|
95
|
+
class FileInfo < FileName
|
96
|
+
attr_accessor(:size, :mtime, :ctime)
|
97
|
+
|
98
|
+
def initialize(theName, thePath = '')
|
99
|
+
super(theName, thePath)
|
100
|
+
@size = 0
|
101
|
+
@isDirectory = false
|
102
|
+
end
|
103
|
+
|
104
|
+
def directory?
|
105
|
+
return @isDirectory
|
106
|
+
end
|
107
|
+
|
108
|
+
def directory!
|
109
|
+
@isDirectory = true
|
110
|
+
end
|
111
|
+
|
112
|
+
def mtimeString
|
113
|
+
timeStringFor(mtime)
|
114
|
+
end
|
115
|
+
|
116
|
+
def ctimeString
|
117
|
+
timeStringFor(ctime)
|
118
|
+
end
|
119
|
+
|
120
|
+
protected
|
121
|
+
|
122
|
+
def timeStringFor(time)
|
123
|
+
return 'unknown' unless time
|
124
|
+
time.strftime("%m/%d/%Y at %I:%M%p")
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class CommentedFile < FileInfo
|
129
|
+
attr_accessor(:comment, :rotation, :width, :height)
|
130
|
+
|
131
|
+
def initialize(theName, thePath = '', theComment = '')EXIFR
|
132
|
+
super(theName, thePath)
|
133
|
+
@comment = theComment
|
134
|
+
@rotation = 0
|
135
|
+
@width = 0
|
136
|
+
@height = 0
|
137
|
+
end
|
138
|
+
|
139
|
+
def rotate?
|
140
|
+
@rotation && @rotation != 0
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# An unordered list of FileInfos, accessible by name.
|
145
|
+
# The directory location is stored as a path-String.
|
146
|
+
class DirectoryContents
|
147
|
+
attr_accessor(:path)
|
148
|
+
|
149
|
+
def initialize(thePath = '')
|
150
|
+
@path = thePath
|
151
|
+
@contents = Hash.new
|
152
|
+
end
|
153
|
+
|
154
|
+
def add(aFileName)
|
155
|
+
@contents[aFileName.name] = aFileName
|
156
|
+
end
|
157
|
+
|
158
|
+
def []=(aName, info)
|
159
|
+
@contents[aName] = info
|
160
|
+
end
|
161
|
+
|
162
|
+
def get(aFileName)
|
163
|
+
return @contents[aFileName.name]
|
164
|
+
end
|
165
|
+
|
166
|
+
def at(aName)
|
167
|
+
return @contents[aName]
|
168
|
+
end
|
169
|
+
|
170
|
+
def [](aName)
|
171
|
+
return @contents[aName]
|
172
|
+
end
|
173
|
+
|
174
|
+
alias has_key? []
|
175
|
+
|
176
|
+
def keys
|
177
|
+
return @contents.keys
|
178
|
+
end
|
179
|
+
|
180
|
+
def directories
|
181
|
+
return @contents.keys.select { |k| @contents[k].directory? }.sort
|
182
|
+
end
|
183
|
+
|
184
|
+
def files
|
185
|
+
return @contents.keys.reject { |k| @contents[k].directory? }.sort
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# A unordered list of FileInfos accessible by FileName.
|
190
|
+
# (Which means that you need both, path and name, to store
|
191
|
+
# the info in the repository. Unless DirectoryContents these
|
192
|
+
# files do not need to have a common location.)
|
193
|
+
class FileRepository
|
194
|
+
def initialize
|
195
|
+
@repository = Hash.new
|
196
|
+
end
|
197
|
+
|
198
|
+
def add(aFileName)
|
199
|
+
self[aFileName] = aFileName
|
200
|
+
end
|
201
|
+
|
202
|
+
def []=(aFileName, info)
|
203
|
+
hashOrNil = @repository[aFileName.path]
|
204
|
+
if hashOrNil == nil
|
205
|
+
hashOrNil = Hash.new
|
206
|
+
@repository[aFileName.path] = hashOrNil
|
207
|
+
end
|
208
|
+
hashOrNil[aFileName.name] = info
|
209
|
+
end
|
210
|
+
|
211
|
+
def get(aFileName)
|
212
|
+
subHash = @repository[aFileName.path]
|
213
|
+
return subHash == nil ? nil : subHash[aFileName.name]
|
214
|
+
end
|
215
|
+
alias [] get
|
216
|
+
alias has_key? get
|
217
|
+
end
|
218
|
+
|
@@ -0,0 +1,106 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'timeout'
|
4
|
+
require 'net/ftp'
|
5
|
+
require 'piggy-core/alive_check'
|
6
|
+
require 'piggy-core/options'
|
7
|
+
|
8
|
+
# This is a wrapper around Net::FTP. It has a different interface
|
9
|
+
# and introduces two additional timeouts. Configure the timeouts
|
10
|
+
# and other parameters through PiggyOptions.
|
11
|
+
class FtpAdapter
|
12
|
+
|
13
|
+
attr_accessor(:host, :user, :password)
|
14
|
+
|
15
|
+
def initialize(options)
|
16
|
+
@connection = nil
|
17
|
+
@options = options # some options may change any time
|
18
|
+
end
|
19
|
+
|
20
|
+
def connect(host, user, password)
|
21
|
+
cTimeout { @connection = Net::FTP.open(host, user, password) }
|
22
|
+
@connection.debug_mode = @options.debug
|
23
|
+
end
|
24
|
+
|
25
|
+
def chdir(path)
|
26
|
+
if path && !path.empty?
|
27
|
+
cTimeout { @connection.chdir(path) }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def nlst
|
32
|
+
cTimeout { @connection.nlst }
|
33
|
+
end
|
34
|
+
|
35
|
+
def info(file, path)
|
36
|
+
info = FileInfo.new(file, path)
|
37
|
+
begin
|
38
|
+
mtime = cTimeout {
|
39
|
+
@connection.mtime(file, @options.ftpHasLocaltime?)
|
40
|
+
}
|
41
|
+
mtime = mtime.localtime unless @options.ftpHasLocaltime?
|
42
|
+
info.mtime = mtime
|
43
|
+
rescue Net::FTPError
|
44
|
+
info.directory!
|
45
|
+
end
|
46
|
+
info
|
47
|
+
end
|
48
|
+
|
49
|
+
def mkdir(dir)
|
50
|
+
cTimeout { @connection.mkdir(dir) }
|
51
|
+
end
|
52
|
+
|
53
|
+
def delete(file)
|
54
|
+
cTimeout { @connection.delete(file) }
|
55
|
+
end
|
56
|
+
|
57
|
+
# Upload with progress events.
|
58
|
+
#
|
59
|
+
# Example:
|
60
|
+
# upload('C:\tmp\bla\big.zip', 'C:\tmp') {
|
61
|
+
# |data|
|
62
|
+
# ...
|
63
|
+
# data.size
|
64
|
+
# ...
|
65
|
+
# }
|
66
|
+
# This will create a remote file bla/big.zip
|
67
|
+
# and every chunk of transferred data will
|
68
|
+
# be given to the block.
|
69
|
+
def upload(localPath, file, &block)
|
70
|
+
depthOffset = FilePath.split(localPath).size
|
71
|
+
pathSections = FilePath.split(file)
|
72
|
+
dirs = pathSections[depthOffset..-2]
|
73
|
+
unless dirs == nil
|
74
|
+
dirs.each do
|
75
|
+
|dir|
|
76
|
+
begin
|
77
|
+
chdir(dir)
|
78
|
+
rescue Net::FTPError
|
79
|
+
mkdir(dir)
|
80
|
+
chdir(dir)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
aliveChecker = AliveCheck.new(@options.ftpTransferTimeout,
|
85
|
+
"ftp transfer timeout")
|
86
|
+
aliveChecker.check do
|
87
|
+
@connection.putbinaryfile(file) { |data|
|
88
|
+
aliveChecker.alive!
|
89
|
+
block.call(data)
|
90
|
+
}
|
91
|
+
end
|
92
|
+
unless dirs == nil
|
93
|
+
dirs.each { |dir| chdir('..') }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def close
|
98
|
+
@connection.close unless @connection.nil?
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def cTimeout(&block)
|
104
|
+
timeout(@options.ftpConnectionTimeout, &block)
|
105
|
+
end
|
106
|
+
end
|