Class: Cifrado::CLI

Inherits:
Thor
  • Object
show all
Includes:
Cifrado, Utils
Defined in:
lib/cifrado/cli.rb,
lib/cifrado/cli/list.rb,
lib/cifrado/cli/post.rb,
lib/cifrado/cli/saio.rb,
lib/cifrado/cli/stat.rb,
lib/cifrado/cli/setup.rb,
lib/cifrado/cli/cinema.rb,
lib/cifrado/cli/delete.rb,
lib/cifrado/cli/upload.rb,
lib/cifrado/cli/configs.rb,
lib/cifrado/cli/jukebox.rb,
lib/cifrado/cli/set_acl.rb,
lib/cifrado/cli/version.rb,
lib/cifrado/cli/download.rb

Constant Summary

Constants included from Cifrado

Log, VERSION

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Utils

#calculate_chunks, #clean_object_name, #decrypt_filename, #encrypt_filename, #humanize_bytes, #mime_type, #prettify_backtrace, #unix_time

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



7
8
9
# File 'lib/cifrado/cli.rb', line 7

def config
  @config
end

Instance Method Details

#cinema(container, video) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/cifrado/cli/cinema.rb', line 7

def cinema(container, video)
  client = client_instance
  
  dir = client.service.directories.get container
  unless dir
    raise "Container #{container} not found."
  end
  
  to_play = dir.files.find { |v| v.key =~ /#{video}/ }
  unless to_play
    raise "No video matches #{video}"
  end

  sub_file = nil
  if options[:subtitles]
    begin
      Log.info "Subtitles found, downloading."
      sub_object = to_play.key.gsub(/\.(avi|mpeg|mov|mkv|ogv|webm)$/, '') + '.srt'
      sub = client.head container, sub_object
      sub_file = "/tmp/#{SecureRandom.hex}"
      client.download container, sub_object, :output => sub_file
    rescue => e
      Log.error "Error downloading subtitles, ignoring."
    end
  end

  $stderr.reopen('/dev/null', 'w')
  pipe = IO.popen(player_command(sub_file), 'w')
  begin
    cb = Proc.new do |total, bytes, segment|
      pipe.write segment if bytes > 0 
    end

    Log.info "#{set_color 'Playing', :bold} #{to_play.key}"
    r = client.stream container, 
                      to_play.key, 
                      :progress_callback => cb
    Log.debug "Video finished streaming"
  rescue => e
    Log.debug "Closing pipe"
    prettify_backtrace e
    pipe.close unless pipe.closed?
    Log.info set_color "\nAdios!", :bold
  end
end

#configsObject



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/cifrado/cli/configs.rb', line 10

def configs
  cdir = Cifrado::Config.instance.config_dir

  current = "#{cdir}/cifradorc"
  if options[:current]
    if File.exist?(current)
      Log.info conceal_password(File.read("#{cdir}/cifradorc"))
      return 'cifradorc'
    else
      raise 'No configuration available.'
    end
  end

  configs = Dir["#{cdir}/cifradorc.*"].map do |c|
    File.basename(c).gsub('cifradorc.', '')
  end

  selected = options[:print]
  if selected and configs.include?(selected)
    Log.info conceal_password(File.read("#{cdir}/cifradorc.#{selected}"))
    return selected
  else
    raise "Config #{selected} not available." if selected
  end
    
  configs.each { |c| Log.info(c) if c !~ /bak\.\d+/ }
  configs
end

#delete(container, object = nil) ⇒ Object



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/cifrado/cli/delete.rb', line 4

def delete(container, object = nil)
  client = client_instance
  if object
    Log.info "Deleting file #{object}..."
    deleted = false
    begin
      client.service.delete_object container, object
      deleted = true
    rescue Fog::Storage::OpenStack::NotFound
      Log.debug 'Trying to find hashed object name'
      file_hash = (Digest::SHA2.new << object).to_s
      deleted = client.service.delete_object(container, file_hash) rescue nil
    end
    if deleted
      Log.info "File #{object} deleted"
    else
      Log.error "File #{object} not found"
    end
  else
    Log.info "Deleting container '#{container}'..."
    dir = client.service.directories.get(container)
    if dir
      dir.files.each do |f|
        Log.info "Deleting file #{f.key}..."
        f.destroy
      end
      dir.destroy
      Log.info "Container #{container} deleted"
    end
  end
end

#download(container, object = nil) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/cifrado/cli/download.rb', line 9

def download(container, object = nil)
  client = client_instance
  downloaded = []
  files = []
  if object
    files << object
  else
    Log.info "Downloading files from container #{container}"
    dir = client.service.directories.get(container)
    files = dir.files if dir
  end
  pb = Progressbar.new 1, 1, :style => options[:progressbar]
  files.each do |f|
    obj = f.is_a?(String) ? f : f.key
    if !f.is_a?(String) and f.[:encrypted_name]
      fname = decrypt_filename f.[:encrypted_name],
                               @config[:password] + @config[:secure_random]
      Log.info "Downloading file #{fname}"
    else
      Log.info "Downloading file #{obj}"
    end
    if client.file_available?(container, obj)
      r = client.download container, obj,
                          :decrypt => options[:decrypt],
                          :passphrase => options[:passphrase],
                          :output => options[:output],
                          :progress_callback => pb.block,
                          :bwlimit => bwlimit
      downloaded << obj
    else 
      Log.debug 'Trying to find hashed object name'
      file_hash = (Digest::SHA2.new << obj).to_s
      r = client.download container, file_hash,
                          :decrypt => options[:decrypt],
                          :passphrase => options[:passphrase],
                          :output => options[:output],
                          :progress_callback => pb.block,
                          :bwlimit => bwlimit
      downloaded << file_hash if r.status == 200
    end
  end
  Log.warn "No files were downloaded." if downloaded.empty?
  downloaded
end

#jukebox(container) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/cifrado/cli/jukebox.rb', line 7

def jukebox(container)
  client = client_instance
  
  dir = client.service.directories.get container
  unless dir
    raise "Container #{container} not found."
  end
  songs = dir.files
  last_exit = Time.now.to_f

  Log.info
  Log.info set_color "Cifrado Jukebox", :bold
  Log.info "---------------"
  Log.info
  Log.info set_color("Ctrl-C once", :bold)+ "   -> next song"
  Log.info set_color("Ctrl-C twice", :bold)+ "  -> quit"
  Log.info
  $stderr.reopen('/dev/null', 'w')
  pipe = IO.popen(player_command, 'w')
  songs.shuffle.each do |song|
    if options[:match] and song.key !~ /#{options[:match]}/i
      next
    end
    begin

      cb = Proc.new do |total, bytes, segment|
        pipe.write segment if bytes > 0 
      end

      unless (song.content_type =~ /ogg|mp3/) or \
              song.key =~ /(mp3|wav|ogg)$/
        next
      end
      Log.info "#{set_color 'Playing', :bold} song"
      Log.info "  * #{song.key}"
      r = client.stream container, 
                        song.key, 
                        :progress_callback => cb
      Log.debug "Song finished streaming"
    rescue Timeout::Error, Errno::EPIPE, Interrupt => e
      Log.debug "Closing pipe"
      prettify_backtrace e
      pipe.close unless pipe.closed?
      Log.debug "Opening new pipe"
      pipe = IO.popen(player_command, 'w')
      if Time.now.to_f - last_exit < 1
        Log.info set_color "\nAdios!", :bold
        return
      else
        last_exit = Time.now.to_f
        Log.info 'Next song...'
      end
    end
  end
ensure
  if pipe and !pipe.closed?
    Log.debug "Closing pipe, killing mplayer"
    Process.kill 'SIGKILL', pipe.pid
    pipe.close 
  end
end

#list(container = nil) ⇒ Object



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/cifrado/cli/list.rb', line 10

def list(container = nil)
  client = client_instance
  list = []
  if container
    dir = client.service.directories.get container
    if dir
      Log.info "Listing objects in '#{container}'"
      files = dir.files
      files.each do |f|
        fname = f.key
        unless options[:decrypt_filenames]
          list << fname
          Log.info fname
          next
        end
        # Skip segments
        next if fname =~ /\/segments\/\d+\.\d{2}\/\d+\/\d+/ and \
                not options[:list_segments]

        # Raises exception if the object is a
        # manifest but there are no segments available
        begin 
           = f.
        rescue Fog::Storage::OpenStack::NotFound
          Log.warn "The file #{fname} is an object manifest, but there are no segments"
          Log.warn "available. I won't be able to decrypt the filename."
          Log.info fname
          list << fname
          next
        end
        if [:encrypted_name] 
          fname = decrypt_filename [:encrypted_name], 
                                   @config[:password] + @config[:secure_random]
          Log.info "#{fname.ljust(55)} #{set_color("[encrypted]",:red, :bold)}"
          Log.info "  hash: #{f.key}" if options[:display_hash]
        else
          Log.info fname.ljust(55)
        end
        list << fname
      end 
      list
    else
      raise "Container '#{container}' not found"
    end
  else
    Log.info "Listing containers"
    directories = client.service.directories
    directories = directories.map { |d| Log.info d.key; d.key }
    directories
  end
end

#post(container, description = '') ⇒ Object



4
5
6
7
8
# File 'lib/cifrado/cli/post.rb', line 4

def post(container, description = '')
  client = client_instance
  client.service.directories.create :key => container, 
                                    :description => description
end

#set_acl(container, object = nil) ⇒ Object



5
6
7
8
# File 'lib/cifrado/cli/set_acl.rb', line 5

def set_acl(container, object = nil)
  client = client_instance
  client.set_acl options[:acl], container
end

#setupObject



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/cifrado/cli/setup.rb', line 4

def setup
  config_instance = Cifrado::Config.instance
  config_file = File.join(config_instance.config_dir, 'cifradorc')
  unless File.directory?(config_instance.config_dir)
    FileUtils.mkdir_p config_instance.config_dir
  end
  if File.exist?(config_file)
    Log.warn "Config file #{set_color config_file, :bold} already exist."
    Log.warn "IMPORTANT: Make sure you backup the current config"
    Log.warn "before saving a new configuration."
    unless yes? "Continue?"
      return
    end
    config = YAML.load_file(config_file)
  else
    config = {}
  end


  puts "Running cifrado setup..."
  puts "Please provide OpenStack/Rackspace/HPCloud credentials."
  puts
  puts "Cifrado can save these settings in #{config_file}"
  puts "for later use."
  puts "The settings (password included) are saved unencrypted."
  puts
  config[:username] = ask(set_color('Username:', :bold))
  system 'stty -echo'
  config[:password] = ask(set_color 'Password:', :bold)
  system 'stty echo'
  puts
  config[:auth_url] = ask(set_color 'Auth URL:', :bold)
  if config[:auth_url] !~ /rackspacecloud\.com/
    config[:tenant]   = ask(set_color('Tenant:', :bold))
  end

  if !config[:secure_random]
    # shit happens
    if RUBY_VERSION >= '1.9'
      config[:secure_random] = SecureRandom.hex.encode('UTF-8')
    else
      config[:secure_random] = SecureRandom.hex
    end
  end

  if yes? "Do you want to save these settings? (y/n) "
    if File.exist?(config_file)
      backup = "#{config_file}.bak.#{Time.now.to_i}"
      FileUtils.cp config_file, backup
      Log.info "Saving backup file to #{backup}."
    end
    File.open(config_file, 'w') do |f| 
      f.puts config.to_yaml
      f.chmod 0600
    end
    @settings_saved = true
    Log.info "Saved!"
  end

  Log.debug "Setup done"
  config
end

#stat(container = nil, object = nil) ⇒ Object



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/cifrado/cli/stat.rb', line 4

def stat(container = nil, object = nil)
  client = client_instance
  creds = client.service.credentials
  mgmt_url = creds[:server_management_url]
  
  reject_headers = ['Accept-Ranges', 'X-Trans-Id']
  unless container and object
    reject_headers << 'Content-Length'
  end
  reject_headers << 'Content-Type' unless object

  object = clean_object_name(object) if object
  headers = client.head(container, object)
  if headers
    puts "Account:".ljust(30) + File.basename(URI.parse(mgmt_url).path)
    headers.sort.each do |k, v| 
      next if reject_headers.include?(k)
      if k == 'X-Timestamp'
        puts "#{(k + ":").ljust(30)}#{v} (#{unix_time(v)})" 
      elsif k == 'X-Account-Bytes-Used' or k == 'Content-Length'
        puts "#{(k + ":").ljust(30)}#{v} (#{humanize_bytes(v)})" 
      elsif k == 'X-Object-Meta-Encrypted-Name'
        puts "#{(k + ":").ljust(30)}#{v}"
      else
        puts "#{(k + ":").ljust(30)}#{v}" 
      end
    end
  else
    if object
      raise "Object not found."
    else
      raise "Container not found."
    end
  end
  headers
end

#upload(container, *args) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/cifrado/cli/upload.rb', line 28

def upload(container, *args)

  if args.empty?
    help 'upload'
    raise "No files specified"
  end
  
  client = client_instance 

  uploaded = []
  args.each do |file|
    unless file and File.exist?(file)
      raise "File '#{file}' does not exist"
    end
    
    if File.directory?(file)
      files = Dir["#{file}/**/*"].reject { |f| File.directory?(f) }
    else
      files = [file]
    end

    files.each do |f|
      begin
        if options[:segments]
          uploaded << split_and_upload(client, container, f)
        else
          headers = client.head container, clean_object_name(f)
          if headers
            if headers['Etag'] == Digest::MD5.file(f).to_s
              if options[:force]
                Log.warn "File #{f} already uploaded and MD5 matches."
                Log.warn "Since --force was used, uploading it again."
                uploaded << upload_single(client, container, f)
              else
                Log.warn "File #{f} already uploaded and MD5 matches, skipping."
              end
            else
              Log.warn "File #{f} already uploaded, but it has changed."
              if options[:force]
                Log.warn "Overwriting it as requested (--force)."
                uploaded << upload_single(client, container, f)
              else
                Log.warn "Since --force was not used, skipping it."
              end
            end
          else
            uploaded << upload_single(client, container, f)
          end
        end
      rescue Errno::ENOENT => e
        Log.error "Error uploading #{f}: " + e.message
      end
    end
  end
  uploaded.flatten
end

#versionObject



4
5
6
# File 'lib/cifrado/cli/version.rb', line 4

def version
  Log.info Cifrado::VERSION
end