Class: Backup::Storage::GoogleDrive

Inherits:
Base
  • Object
show all
Includes:
Storage::Cycler
Defined in:
lib/storage/googledrive.rb

Defined Under Namespace

Classes: Error

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model, storage_id = nil) ⇒ GoogleDrive

Creates a new instance of the storage object

Raises:



23
24
25
26
27
28
29
30
31
32
33
# File 'lib/storage/googledrive.rb', line 23

def initialize(model, storage_id = nil)
  super

  @path           ||= 'backups'
  path.sub!(/^\//, '')

  required = %w{ refresh_token }
  raise Error, "Configuration Error: a refresh_token is required" if refresh_token.nil?

  raise Error, "Configuration Error: gdrive executable is required." if gdrive_exe.nil?
end

Instance Attribute Details

#gdrive_exeObject

Path to gdrive executable



15
16
17
# File 'lib/storage/googledrive.rb', line 15

def gdrive_exe
  @gdrive_exe
end

#refresh_tokenObject

Use the gdrive executable to obtain a refresh token. Add that token to your backup model. The gdrive exe will handle refresing the access tokens



19
20
21
# File 'lib/storage/googledrive.rb', line 19

def refresh_token
  @refresh_token
end

Instance Method Details

#find_id_from_path(path = remote_path, create = true) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
# File 'lib/storage/googledrive.rb', line 65

def find_id_from_path(path = remote_path, create = true)
  parent = nil
  path.split('/').each do |path_part|
    id = get_folder_id(path_part, parent)
    if id.to_s.empty? && create
      id = gdrive_mkdir(path_part, parent)
    end
    parent = id
  end
  return parent
end

#gdrive_delete(id, recursive = true) ⇒ Object



138
139
140
141
142
143
144
145
146
# File 'lib/storage/googledrive.rb', line 138

def gdrive_delete(id, recursive = true)
  cmd = "gdrive --refresh-token '#{refresh_token}' delete #{'-r' if recursive} '#{id}'"
  output = `#{cmd}`
  if output.downcase.include? "error"
    raise Error, "Could not delete object with id: #{id}. See gdrive output: #{output}"
  else
    Logger.info output
  end
end

#gdrive_list(query) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/storage/googledrive.rb', line 83

def gdrive_list(query)
  unless query.empty?
    cmd = "gdrive --refresh-token '#{refresh_token}' list --no-header -q \"#{query}\""
    output = `#{cmd}`
    if output.downcase.include? "error"
      raise Error, "Could not list or find the object with query string '#{query}'. gdrive output: #{output}"
    elsif output.empty?
      return nil
    else
      begin
        return /^([^ ]*).*/.match(output)[1] # will return an empty string on no match
      rescue => err
        return nil
      end
    end
  else
    raise Error, "A search query is required to list/find a file or folder"
  end
end

#gdrive_mkdir(name, parent = nil) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/storage/googledrive.rb', line 103

def gdrive_mkdir(name, parent = nil)
  unless name.empty?
    parent = parent ? parent : 'root'
    cmd = "gdrive --refresh-token '#{refresh_token}' mkdir -p '#{parent}' '#{name}'"
    output = `#{cmd}`
    if output.downcase.include? "error"
      raise Error, "Could not create the directory '#{name}' with parent '#{parent}'. gdrive output: #{output}"
    else
      id = /^Directory (.*?) created/.match(output)[1]
      raise Error, "Could not determine ID of newly created folder. See gdrive output: #{output}" if id.to_s.empty?
      Logger.info "Created folder #{name} successfully with id '#{id}'"
      return id
    end
  else
    raise Error, "Name parameter is required to make a directory"
  end
end

#gdrive_upload(src, parent = nil) ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/storage/googledrive.rb', line 121

def gdrive_upload(src, parent = nil)
  parent = parent ? parent : 'root'
  cmd = "gdrive --refresh-token '#{refresh_token}' upload -p '#{parent}' '#{src}'"
  output = `#{cmd}`
  if ( ["error", "failed"].any? {|s| output.downcase.include? s } )
    raise Error, "Could not upload file. See gdrive output: #{output}"
  else
    begin
      id = /.*Uploaded (.*?) .*/.match(output)[1]
      raise Error, "empty id" if id.to_s.empty?
      Logger.info "Uploaded #{src} into parent folder '#{parent}' successfully. Google Drive file_id: #{ id }"
    rescue => err
      raise Error.wrap(err, "Could not determine ID of newly created folder. See gdrive output: #{output}")
    end
  end
end

#get_folder_id(name, parent = nil) ⇒ Object



78
79
80
81
# File 'lib/storage/googledrive.rb', line 78

def get_folder_id(name, parent = nil)
  parent = parent ? parent : 'root'
  gdrive_list("name = '#{name}' and '#{parent}' in parents")
end

#remove!(package) ⇒ Object

# Called by the Cycler. # Any error raised will be logged as a warning.



55
56
57
58
59
60
61
62
63
# File 'lib/storage/googledrive.rb', line 55

def remove!(package)
  Logger.info "Removing backup package dated #{ package.time }..."
  id = find_id_from_path(remote_path_for(package), false)
  if id.to_s.empty?
    raise Error, "Backup packge #{ package.time } not found in Google Drive"
  else
    gdrive_delete(id)
  end
end

#transfer!Object

Transfer each of the package files to Dropbox in chunks of chunk_size. Each chunk will be retried chunk_retries times, pausing retry_waitsec between retries, if errors occur.



42
43
44
45
46
47
48
49
50
51
# File 'lib/storage/googledrive.rb', line 42

def transfer!
  package.filenames.each do |filename|
    src = File.join(Config.tmp_path, filename)
    dest = File.join(remote_path, filename)
    Logger.info "Storing '#{ dest }'..."

    parent_id = find_id_from_path(remote_path)
    gdrive_upload(src, parent_id)
  end
end