AI君がコーディングした。
lan_share.rb
require "bundler/setup"
require 'sinatra/base'
require 'puma'
require 'faraday'
require 'faraday/multipart'
require 'fileutils'
require 'socket'
require 'find'
PORT = 4567
DISCOVERY_PORT = 4568
DISCOVERY_MSG = "RUBY_LAN_SHARE_SERVER_HERE"
SAVE_DIR = File.expand_path("~/Downloads/lan_share")
class LANReceiver < Sinatra::Base
configure do
set :server, :puma
set :bind, '0.0.0.0'
set :port, PORT
set :logging, false
end
def self.start_beacon_thread
Thread.new do
udp_socket = UDPSocket.new
udp_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
loop do
udp_socket.send(DISCOVERY_MSG, 0, '255.255.255.255', DISCOVERY_PORT)
sleep 1
rescue => e
sleep 2
end
end
end
post '/upload' do
type = request.env['HTTP_X_TYPE']
if type == 'dir_mkdir'
relative_path = request.env['HTTP_X_RELATIVE_PATH'].to_s.force_encoding('UTF-8')
extract_path = File.join(SAVE_DIR, relative_path)
FileUtils.mkdir_p(extract_path)
puts "📁 フォルダを作成しました: #{relative_path}"
return "OK"
end
unless params[:file]
status 400
return "エラー: ファイルがありません"
end
tempfile = params[:file][:tempfile]
relative_path = request.env['HTTP_X_RELATIVE_PATH'].to_s.force_encoding('UTF-8')
extract_path = File.join(SAVE_DIR, relative_path)
FileUtils.mkdir_p(File.dirname(extract_path))
File.open(extract_path, 'wb') do |f|
f.write(tempfile.read)
end
size_mb = (tempfile.size / 1024.0 / 1024.0).round(2)
puts "💾 ファイル受信完了: #{relative_path} (#{size_mb} MB)"
"OK"
end
end
def discover_target_ip
puts "🔍 LAN内の転送相手を自動検索中..."
udp_socket = UDPSocket.new
udp_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
udp_socket.bind('0.0.0.0', DISCOVERY_PORT)
return nil unless IO.select([udp_socket], nil, nil, 5)
loop do
msg, sender = udp_socket.recvfrom_nonblock(1024)
if msg == DISCOVERY_MSG
target_ip = sender[3]
next if Socket.ip_address_list.any? { |ip| ip.ip_address == target_ip }
puts "✨ 相手を発見しました! IPアドレス: #{target_ip}"
return target_ip
end
end
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
puts "❌ 相手が見つかりませんでした。"
nil
ensure
udp_socket.close if udp_socket
end
def upload_file(conn, local_file_path, relative_path)
payload = { file: Faraday::Multipart::FilePart.new(local_file_path, 'application/octet-stream') }
response = conn.post('/upload', payload) do |req|
req.headers['X-Relative-Path'] = relative_path.to_s.force_encoding('UTF-8')
req.headers['X-Type'] = 'file'
end
response.status == 200 && response.body == "OK"
end
def make_remote_dir(conn, relative_path)
response = conn.post('/upload') do |req|
req.headers['X-Relative-Path'] = relative_path.to_s.force_encoding('UTF-8')
req.headers['X-Type'] = 'dir_mkdir'
end
response.status == 200 && response.body == "OK"
end
def send_to_target(path)
unless File.exist?(path)
puts "❌ エラー: 指定されたパスが存在しません: #{path}"
return
end
target_ip = discover_target_ip
return if target_ip.nil?
conn = Faraday.new(url: "http://#{target_ip}:#{PORT}") do |f|
f.request :multipart
f.request :url_encoded
f.adapter :net_http
end
if File.directory?(path)
puts "📂 ディレクトリ構造を解析中..."
base_dir = File.basename(path)
directories = []
files = []
Find.find(path) do |p|
next if p == path
next if File.symlink?(p)
if File.directory?(p)
directories << p
elsif File.file?(p)
files << p
end
end
puts "🚀 遠隔地にフォルダ構造を同期中... (#{directories.size}個のフォルダ)"
directories.each do |dir|
rel_path = File.join(base_dir, dir.sub("#{path}/", ""))
make_remote_dir(conn, rel_path)
end
puts "🚀 ファイル群のダイレクト転送を開始します... (#{files.size}個のファイル)"
success_count = 0
files.each_with_index do |file, idx|
rel_path = File.join(base_dir, file.sub("#{path}/", ""))
print "\r転送中: [#{idx + 1}/#{files.size}] #{File.basename(file)}... "
if upload_file(conn, file, rel_path)
success_count += 1
else
puts "\n⚠️ 転送失敗: #{rel_path}"
end
end
puts "\n✨ ディレクトリ全体の転送が完了しました!"
puts " 成果: 成功 #{success_count} / 全体 #{files.size}"
else
puts "🚀 単一ファイルを転送中: #{File.basename(path)}"
if upload_file(conn, path, File.basename(path))
puts "✨ 転送が正常に完了しました!"
else
puts "❌ 転送に失敗しました"
end
end
rescue => e
puts "\n❌ 通信エラーが発生しました: #{e.message}"
end
if ARGV.empty?
puts "🚀 LAN内 ファイル受信サーバーを起動します... (Port: #{PORT})"
puts "保存先ディレクトリ: #{SAVE_DIR}"
puts "--------------------------------------------------"
LANReceiver.start_beacon_thread
LANReceiver.run!
else
path = ARGV[0]
if path
send_to_target(File.expand_path(path))
else
puts "【使い方】"
puts " サーバー起動: ruby lan_share.rb"
puts " ファイル送信: ruby lan_share.rb [ファイルやフォルダのパス]"
end
end
使い方
コマンド化した。まず~/Documents/Ruby400/において(いまのところ、どこの場所でも可能、というわけではない)
$ lan_share
でサーバーを立ち上げる。
転送元は
$ lan_share ./sample.txt
これでサーバー側の~/Downloads/lan_share/内にファイルまたはディレクトリが転送される。複雑な構造のディレクトリも丸ごと転送可能。
ちなみに
Address already in use - bind(2) for "0.0.0.0" port 4567 (Errno::EADDRINUSE)
のエラーが出た場合は、~/.bashrcに
alias killport='_killport(){ kill -9 $(lsof -t -i :$1); }; _killport'を追加して、
$ killport 4567
とかする。