Class: Henchman::DropboxAssistant

Inherits:
Object
  • Object
show all
Defined in:
lib/dbx_assistant.rb

Instance Method Summary collapse

Constructor Details

#initialize(config, debug) ⇒ DropboxAssistant

Returns a new instance of DropboxAssistant.



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/dbx_assistant.rb', line 9

def initialize config, debug
  begin
    @search_limit = 500
    @debug  = debug
    @config = config
    @client = Dropbox::Client.new @config[:dropbox][:access_token]

    # stop words from http://nlp.stanford.edu/IR-book/html/htmledition/dropping-common-terms-stop-words-1.html
    @stop_words = ['a', 'an', 'and', 'are', 'as', 'at', 'be', 'by', 'for', 'from',
                   'has', 'he', 'in', 'is', 'it', 'its', 'of', 'on', 'that', 'the',
                   'to', 'was', 'were', 'will', 'with']
    true
  rescue DropboxError => msg
    puts "#{DateTime.now.strftime('%m-%d-%Y %H:%M:%S')}|"\
         "Couldn't connect to Dropbox (#{msg}). "\
         "Run `henchman stop` then `henchman configure` "\
         "to configure Dropbox connection."
    false
  end
end

Instance Method Details

#download(selection, dropbox_path) ⇒ Object



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
# File 'lib/dbx_assistant.rb', line 30

def download selection, dropbox_path
  puts "#{DateTime.now.strftime('%m-%d-%Y %H:%M:%S')}|"\
       "Downloading #{selection.reject{|k,v| k == :path || k == :id}.values.join(':')} from #{dropbox_path}"
  begin
    # download the file
    content, body = @client.download dropbox_path

    # make sure we have the directory to put it in
    trgt_dir = File.join @config[:root], selection[:artist], selection[:album]
    puts trgt_dir if @debug
    system 'mkdir', '-p', trgt_dir

    # save the file
    file_save_path = File.join trgt_dir, File.basename(dropbox_path)
    puts file_save_path if @debug
    open(file_save_path, 'w') {|f| f.puts body }
    file_save_path
  rescue DropboxError => msg
    puts "#{DateTime.now.strftime('%m-%d-%Y %H:%M:%S')}|"\
         "Error downloading Dropbox file #{dropbox_path}: #{msg}"
    false
  rescue StandardError => msg
    puts "#{DateTime.now.strftime('%m-%d-%Y %H:%M:%S')}|"\
         "Error saving Dropbox file #{dropbox_path} to #{trgt_dir}: #{msg}"
    false
  end
end

#get_results(track, artist) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/dbx_assistant.rb', line 76

def get_results track, artist
  puts "`get_results` for #{track} by #{artist}" if @debug
  # Search Dropbox for the file
  # We're only not filtering to get files because we want to check if we get 1000 results
  # (i.e. a maxed out playload) back. This is because the filtering happens in OUR client,
  # not in the Dropbox search. We're doing a simple search on only track name because we
  # want to minimize the number of network calls, and USUALLY this is good enough
  results = search track

  # If we get 1000 results back, it means we probably have a REALLY simple song title
  # and we're not assured to have the target song in our payload, since we maxed it
  # out. So, we'll do another search call on the artist
  if results.length == @search_limit
    puts "Maximum (#{@search_limit}) results returned" if @debug
    results.clear
    album_dirs = search artist, :dir
    album_dirs.each do |album|
      tmp_rslts = search track, :file, album['path']
      results.push(*tmp_rslts)
    end
  else    # Otherwise, filter off all the directories and things without the right extensions
    puts "Filtering out directories and incorrect file extensions" if @debug
    results.reject! { |result| !result.is_a? Dropbox::FileMetadata ||
                               !(@config[:file_extensions].include?(File.extname(result.path_lower)[1..-1])) }
  end
  puts "Returning #{results.length} results from `get_results`" if @debug
  return results
end

#search(value, filter = nil, dir = @config[:dropbox]) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/dbx_assistant.rb', line 58

def search value, filter = nil, dir = @config[:dropbox][:root]
  puts "Searching for #{value} in #{dir}, filtering by <#{filter}>" if @debug
  begin
    results = @client.search dir, value, 0, @search_limit
    puts "#{results.length} total results found" if @debug
    if filter == :dir
      results.reject! { |result| !result.is_a? Dropbox::FolderMetadata }
    elsif filter == :file
      results.reject! { |result| !result.is_a? Dropbox::FileMetadata ||
                                 !(@config[:file_extensions].include?(File.extname(result.path_lower)[1..-1])) }
    end
    puts "Returning #{results.length} results for `search` (after filtering)" if @debug
    return results
  rescue DropboxError => msg
    raise "Error accessing Dropbox Search API|#{value}|#{dir}|#{msg}"
  end
end

#search_for(selection) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/dbx_assistant.rb', line 105

def search_for selection
  puts "#{DateTime.now.strftime('%m-%d-%Y %H:%M:%S')}|"\
       "Searching for #{selection.reject{|k,v| k == :path || k == :id}.values.join(':')}"

  results = get_results selection[:track], selection[:artist]

  # if we still don't have any results, try dropping any brackets and paranthesis
  if results.empty? && (selection[:track].match(%r( *\[.*\] *)) || selection[:track].match(%r( *\(.*\) *)))
    puts "No results. Trying without brackets or parenthesis" if @debug
    track = selection[:track].gsub(%r( *\[.*\] *), " ").gsub(%r( *\(.*\) *), " ")
    results = get_results track, selection[:artist]
  end

  # if there were no results, raise err
  if results.empty?
    raise "Track not found in Dropbox: #{selection.reject{|k,v| k == :id}.values.join(':')}"
  else
    scores       = Hash.new 0
    tokens       = Array.new
    track_tokens = Array.new
    [:artist, :album].each do |identifier|
      tokens |= selection[identifier].downcase
                                     .gsub(%r( +), " ")
                                     .gsub(%r(-+), "-")
                                     .strip
                                     .split(/[\s-]/)
                                     .delete_if{ |t| @stop_words.include? t }
    end
    @config[:file_extensions].each do |extension|
      track_tokens |= selection[:track].downcase
                                       .gsub(%r( +), " ")
                                       .gsub(%r(-+), "-")
                                       .strip
                                       .split(/[\s-]/)
                                       .map { |t| "#{t}.#{extension}" }
    end

    if @debug
      puts ":artist and :album tokens: #{tokens.inspect}"
      puts ":track tokens: #{track_tokens.inspect}"
    end

    results.each do |result|
      dir = "#{File.dirname(result.path_lower)}/"
      basename = " #{File.basename(result.path_lower)}"
      tokens.each do |token|
        if dir =~ %r(.*[\s\/-]#{token}[\s\/-].*)
          puts "Token #{token} found in #{dir}" if @debug
          if results.length == 1
            return result.path_display
          else
            scores[result.path_display] += 1
          end
        end
      end
      track_tokens.each do |token|
        if basename =~ %r([.]*[\s-]+#{token})
          puts "Token #{token} found in #{basename}" if @debug
          scores[result.path_display] += 1
        end
      end
    end

    # if the we only had one track and we're here, that means the path didn't contain
    # any of the album or artist tokens, so we'll say we couldn't find it
    if results.length == 1
      raise "Track not found in Dropbox: #{selection.reject{|k,v| k == :id}.values.join(':')}"
    end

    # return the path that has the highest score
    scores = scores.sort_by { |path, score| score }
    if @debug
      scores.each { |score| puts score.join ': ' }
    end
    scores[-1][0]
  end
end