Created
January 25, 2025 06:59
-
-
Save arthur-abogadil/e0a6c4c63528f7c2da72895d41b47a7f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'net/sftp' | |
require 'net/ssh' | |
require 'zoho_drive' | |
require 'fileutils' | |
require 'logger' | |
require 'zip' | |
require 'rubygems/package' | |
require 'zlib' | |
class SFTPWatcher | |
COMPRESSED_EXTENSIONS = ['.zip', '.gz', '.tar.gz', '.tgz'] | |
def initialize(sftp_config, zoho_config, watch_interval = 60) | |
# SFTP Configuration | |
@sftp_host = sftp_config[:host] | |
@sftp_user = sftp_config[:username] | |
@sftp_password = sftp_config[:password] | |
@sftp_path = sftp_config[:remote_path] | |
# Zoho Drive Configuration | |
@zoho_access_token = zoho_config[:access_token] | |
@zoho_folder_id = zoho_config[:folder_id] | |
# Local tracking | |
@processed_files = Set.new | |
@watch_interval = watch_interval | |
# Logging | |
@logger = Logger.new('sftp_sync.log', 10, 1024000) | |
@logger.level = Logger::INFO | |
end | |
def start_watching | |
@logger.info "Starting SFTP folder watcher for #{@sftp_path}" | |
loop do | |
begin | |
watch_and_sync | |
sleep @watch_interval | |
rescue StandardError => e | |
@logger.error "Error during folder watch: #{e.message}" | |
@logger.error e.backtrace.join("\n") | |
sleep @watch_interval | |
end | |
end | |
end | |
private | |
def watch_and_sync | |
Net::SSH.start(@sftp_host, @sftp_user, password: @sftp_password) do |ssh| | |
ssh.sftp.connect do |sftp| | |
current_files = list_remote_files(sftp) | |
new_files = current_files - @processed_files | |
new_files.each do |file| | |
begin | |
download_and_upload(sftp, file) | |
@processed_files.add(file) | |
rescue StandardError => e | |
@logger.error "Failed to process file #{file}: #{e.message}" | |
end | |
end | |
end | |
end | |
end | |
def list_remote_files(sftp) | |
files = sftp.dir.entries(@sftp_path) | |
.select { |f| f.file? && !f.name.start_with?('.') } | |
.map { |f| f.name } | |
@logger.info "Found #{files.size} files in SFTP directory" | |
files.to_set | |
end | |
def download_and_upload(sftp, filename) | |
local_temp_path = File.join(Dir.tmpdir, filename) | |
remote_path = File.join(@sftp_path, filename) | |
# Download file from SFTP | |
sftp.download!(remote_path, local_temp_path) | |
@logger.info "Downloaded #{filename} to local temp" | |
# Unzip/extract if compressed | |
unzipped_files = unpack_file(local_temp_path) | |
# Upload unzipped files to Zoho Drive | |
zoho_client = ZohoDrive::Client.new(@zoho_access_token) | |
unzipped_files.each do |unzipped_file| | |
zoho_client.upload_file( | |
file_path: unzipped_file, | |
folder_id: @zoho_folder_id | |
) | |
@logger.info "Uploaded #{File.basename(unzipped_file)} to Zoho Drive" | |
end | |
# Cleanup | |
FileUtils.rm(local_temp_path) | |
unzipped_files.each { |f| FileUtils.rm(f) } | |
rescue StandardError => e | |
@logger.error "Error processing #{filename}: #{e.message}" | |
raise | |
end | |
def unpack_file(file_path) | |
extension = File.extname(file_path).downcase | |
unzipped_files = [] | |
case extension | |
when '.zip' | |
unzipped_files = unzip_file(file_path) | |
when '.gz' | |
unzipped_files = [gunzip_file(file_path)] | |
when '.tar.gz', '.tgz' | |
unzipped_files = untar_gz_file(file_path) | |
else | |
unzipped_files = [file_path] | |
end | |
unzipped_files | |
end | |
def unzip_file(file_path) | |
unzipped_files = [] | |
Zip::File.open(file_path) do |zip| | |
zip.each do |entry| | |
extracted_path = File.join(Dir.tmpdir, entry.name) | |
entry.extract(extracted_path) | |
unzipped_files << extracted_path | |
end | |
end | |
unzipped_files | |
end | |
def gunzip_file(file_path) | |
output_path = file_path.chomp('.gz') | |
Zlib::GzipReader.open(file_path) do |gz| | |
File.open(output_path, 'w') do |f| | |
f.write(gz.read) | |
end | |
end | |
output_path | |
end | |
def untar_gz_file(file_path) | |
unzipped_files = [] | |
Gem::Package::TarReader.new(Zlib::GzipReader.open(file_path)) do |tar| | |
tar.each do |entry| | |
if entry.file? | |
extracted_path = File.join(Dir.tmpdir, entry.full_name) | |
FileUtils.mkdir_p(File.dirname(extracted_path)) | |
File.open(extracted_path, 'wb') { |f| f.write(entry.read) } | |
unzipped_files << extracted_path | |
end | |
end | |
end | |
unzipped_files | |
end | |
end | |
# Example usage | |
sftp_config = { | |
host: 'your.sftp.host', | |
username: 'your_username', | |
password: 'your_password', | |
remote_path: '/path/to/watch/folder' | |
} | |
zoho_config = { | |
access_token: 'your_zoho_access_token', | |
folder_id: 'zoho_drive_folder_id' | |
} | |
watcher = SFTPWatcher.new(sftp_config, zoho_config) | |
watcher.start_watching |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
System Service Setup (Linux/macOS):
$ sudo nano /etc/systemd/system/sftp-zoho-sync.service
[Unit]
Description=SFTP to Zoho Drive Sync Service
After=network.target
[Service]
Type=simple
User=your_username
WorkingDirectory=/path/to/script/directory
ExecStart=/usr/bin/ruby /path/to/script/sftp_watcher.rb
Restart=on-failure
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable sftp-zoho-sync
sudo systemctl start sftp-zoho-sync