Skip to content

Instantly share code, notes, and snippets.

@arthur-abogadil
Created January 25, 2025 06:59
Show Gist options
  • Save arthur-abogadil/e0a6c4c63528f7c2da72895d41b47a7f to your computer and use it in GitHub Desktop.
Save arthur-abogadil/e0a6c4c63528f7c2da72895d41b47a7f to your computer and use it in GitHub Desktop.
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
@arthur-abogadil
Copy link
Author

System Service Setup (Linux/macOS):

  1. In bash

$ sudo nano /etc/systemd/system/sftp-zoho-sync.service

  1. Add Service Configuration

[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

  1. Enable and Start Service

sudo systemctl daemon-reload
sudo systemctl enable sftp-zoho-sync
sudo systemctl start sftp-zoho-sync

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment