#!/usr/bin/env ruby # # dcp: copy files to/from dropbox like scp command # # Copyright (C) 2017 by Tada, Tadashi # Distributed under GPL # require 'dcp' require 'optparse' require 'pit' module DCP class MultiError < StandardError attr_reader :messages def initialize(messages) @messages = messages end end class CLI def run @opts, args = parse_options begin pairs = make_pairs(args.pop, args) rescue MultiError => e e.messages.each{|m| error m} exit 1 rescue => e error e.message exit 1 end begin pairs.each do |src, dst| puts "#{src} => #{dst}" if @opts[:debug] open(src, 'r') do |r| open(dst, 'w') do |w| while data = r.read(10_000_000) do w.write(data) print '.' unless @opts[:quiet] end end end puts if @opts[:debug] end puts unless @opts[:quiet] rescue => e error "#{e.message} (#{e.class})" exit 1 end end private def error(msg) $stderr.puts "dcp: #{msg}" $@.each{|e| $stderr.puts e} if @opts && @opts[:debug] end def parse_options(argv = ARGV) op = OptionParser.new self.class.module_eval do define_method(:usage) do |msg = nil| puts op.to_s error msg if msg exit 1 end end opts = { quiet: false, debug: false } op.on('-q', '--quiet', "quiet mode") do opts[:quiet] = true end op.on('-D', '--debug', "debug mode") do opts[:debug] = true end op.on('-h', '--help', "print this messages") do usage end op.banner += ' [db:]SRC [SRC...] [db:]DEST' begin args = op.parse(argv) rescue OptionParser::InvalidOption => e usage e.message end usage 'needs src and dest' if args.size < 2 return opts, args end def dropbox return @dropbox if @dropbox auth = ::Pit::get('dcp') unless auth[:dropbox_token] print "Enter dropbox app key: " api_key = $stdin.gets.chomp print "Enter dropbox app secret: " api_secret = $stdin.gets.chomp authenticator = ::DropboxApi::Authenticator.new(api_key, api_secret) puts "\nGo to this url and click 'Authorize' to get the token:" puts authenticator.authorize_url print "Enter the token: " code = $stdin.gets.chomp auth[:dropbox_token] = authenticator.get_token(code).token Pit::set('dcp', data: auth) end return @dropbox = Dropbox.new(auth[:dropbox_token]) end def dropbox_path?(path) path =~ /\A(db|dropbox):/ end def dropbox_path(file) raise StandardError.new("not a dropbox path: #{file}") unless dropbox_path?(file) file.split(/:/, 2)[1] end def make_pairs(dst, srcs) errors = [] dst_is_directory = false # cheking src file existance srcs.each do |src| begin if dropbox_path?(src) errors << "cannot copy from dropbox: #{src}" else if directory?(src) errors << "cannot copy from directory: #{src}" end end rescue Errno::ENOENT errors << "file not found: #{src}" end end # cheking dst file correctness begin d = dst =~ /\/\z/ ? dst.chop : dst # root directory => '' if file?(d) if srcs.size > 1 errors << "cannot copy multiple files into a file: #{dst}" end elsif directory?(d) dst_is_directory = true end rescue Errno::ENOENT # check parent directory existance begin if directory?(File.dirname(d)) && srcs.size > 1 errors << "cannot copy multiple files into a file: #{dst}" end rescue Errno::ENOENT errors << "file not found: #{dst}" end end raise MultiError.new(errors) unless errors.empty? if dst_is_directory return srcs.map{|src| [src, "#{d}/#{File.basename(src)}"]} else return [[srcs[0], dst]] end end def open(file, opts) if dropbox_path?(file) dropbox.open(dropbox_path(file), opts){|f| yield f} else File.open(file, opts){|f| yield f} end end def file?(file) if dropbox_path?(file) dropbox.file?(dropbox_path(file)) else File.stat(file).file? end end def directory?(file) if dropbox_path?(file) dropbox.directory?(dropbox_path(file)) else File.stat(file).directory? end end end end DCP::CLI.new.run