henchman-sync 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1f864ccdbb1adb044b16389e17adf74cc7757d26
4
- data.tar.gz: 0ed7300956a883823681114bca11bb6985557c4b
3
+ metadata.gz: 83e05f1424105e5e73efd0efcd56291a7167d002
4
+ data.tar.gz: 8f5047f31c81304402b96a91563b8ac0614c0b59
5
5
  SHA512:
6
- metadata.gz: 43a0697682a79bd283b855ec7c1c59dc7f6de7a6547a9b523effeb8e66dce7f7775283e3c93ee961be6ce4fb15f7238adb2f6f96192625c25ecaf21ebb8e9a1f
7
- data.tar.gz: b38df428d8700ac444c63ba55fc2dffa778e9b7cbd06f0732c02385e05e41ffaac9ccc1c4c8ce930b165e3beaa4f2c60b7ba8055dc857e3f955d3610dbfe7523
6
+ metadata.gz: a69d6220254d81047b7826d8c318035e67de8e8f2d7e0aff3cdf37cfc3b38a2db60322a5fffb27e2388399455cde7394e6984fa971fa51e6ef74f4ebd00e0487
7
+ data.tar.gz: 46772a0505241b492ea0e3e4e4a49225acca0f46cbe63c895f57cc92352db3c2a962b210c1cc0995a9359c32ab95286db671e302205f545517566096d65021b5
data/README.md CHANGED
@@ -6,11 +6,9 @@ When you (single) click on a track in iTunes the application will check if that
6
6
 
7
7
  ## Installation
8
8
 
9
- Add this line to your application's Gemfile:
9
+ Install it yourself as:
10
10
 
11
- Or install it yourself as:
12
-
13
- $ gem install henchman
11
+ $ gem install henchman-sync
14
12
 
15
13
  ## Usage
16
14
 
@@ -36,6 +34,12 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
36
34
 
37
35
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
38
36
 
37
+ ## TODO
38
+
39
+ - Add job to cleanup tracks that haven't been in X number of minutes
40
+ - Figure out how to download playlist with a "'" in them
41
+ - Move off of the 'dropbox-sdk' gem, since it uses their API V1, which will be depreciated June 28th, 2017 (or just buck up and update the gem myself)
42
+
39
43
  ## Contributing
40
44
 
41
45
  Bug reports and pull requests are welcome on GitHub at https://github.com/gremerritt/henchman.
data/lib/applescript.rb CHANGED
@@ -13,19 +13,16 @@ module Henchman
13
13
  "end tell"
14
14
  end
15
15
 
16
- def prompt_script
16
+ def prompt_script prompt
17
17
  "tell application \"iTunes\"\n"\
18
- " display dialog \"Fetch?\"\n"\
18
+ " display dialog \"Fetch #{prompt}?\"\n"\
19
19
  "end tell"
20
20
  end
21
21
 
22
- def update_track_location_script selection, local_file
22
+ def update_track_location_script track_id, local_file
23
23
  "tell application \"iTunes\"\n"\
24
24
  " try\n"\
25
- " set data_tracks to "\
26
- " (every track whose artist is \"#{selection[:artist].gsub(/'/){ %q('"'"') }}\" and "\
27
- " album is \"#{selection[:album].gsub(/'/){ %q('"'"') }}\" and "\
28
- " name is \"#{selection[:track].gsub(/'/){ %q('"'"') }}\")\n"\
25
+ " set data_tracks to (every track whose database ID is \"#{track_id}\")\n"\
29
26
  " if (count of data_tracks) is 1 then\n"\
30
27
  " set location of (item 1 of data_tracks) to "\
31
28
  " (POSIX file \"#{local_file.gsub(/'/){ %q('"'"') }}\")\n"\
@@ -46,10 +43,12 @@ module Henchman
46
43
  " set data_artist to artist of selection as string\n"\
47
44
  " set data_album to album of selection as string\n"\
48
45
  " set data_title to name of selection as string\n"\
46
+ " set data_id to database ID of selection as string\n"\
49
47
  " set data_location to POSIX path of (location of selection as string)\n"\
50
48
  " set str to data_artist & \"#{@delimiter}\" & "\
51
49
  " data_album & \"#{@delimiter}\" & "\
52
50
  " data_title & \"#{@delimiter}\" & "\
51
+ " data_id & \"#{@delimiter}\" & "\
53
52
  " data_location\n"\
54
53
  " return str\n"\
55
54
  " end if\n"\
@@ -62,17 +61,94 @@ module Henchman
62
61
  def get_album_tracks_script artist, album
63
62
  "tell application \"iTunes\"\n"\
64
63
  " try\n"\
65
- " set data_tracks to "\
64
+ " set album_tracks to "\
66
65
  " (every track whose artist is \"#{artist}\" "\
67
66
  " and album is \"#{album}\")\n"\
68
- " set data_tracks_str to \"\"\n"\
69
- " repeat with data_track in data_tracks\n"\
70
- " set data_tracks_str to data_tracks_str & (name of data_track) as string\n"\
71
- " if (name of (last item of data_tracks)) is not (name of data_track) then\n"\
72
- " set data_tracks_str to data_tracks_str & \"#{@delimiter}\"\n"\
73
- " end if\n"\
67
+ " set str to \"\"\n"\
68
+ " repeat with album_track in album_tracks\n"\
69
+ " set data_location to location of album_track as string\n"\
70
+ " if data_location is equal to \"missing value\" then\n"\
71
+ " set data_artist to artist of album_track as string\n"\
72
+ " set data_album to album of album_track as string\n"\
73
+ " set data_title to name of album_track as string\n"\
74
+ " set data_id to database ID of album_track as string\n"\
75
+ " set str to str & data_artist & \"#{@delimiter}\" "\
76
+ " & data_album & \"#{@delimiter}\" "\
77
+ " & data_title & \"#{@delimiter}\" "\
78
+ " & data_id & \"#{@delimiter*2}\"\n"\
79
+ " end if\n"\
74
80
  " end repeat\n"\
75
- " return data_tracks_str\n"\
81
+ " return str\n"\
82
+ " on error\n"\
83
+ " return 0\n"\
84
+ " end try\n"\
85
+ "end tell"
86
+ end
87
+
88
+ def get_playlist_script
89
+ "tell application \"iTunes\"\n"\
90
+ " try\n"\
91
+ " set selected_playlist to (get view of front window)\n"\
92
+ " set playlist_name to name of selected_playlist as string\n"\
93
+ " set playlist_special to special kind of selected_playlist as string\n"\
94
+ " set str to playlist_name & \"#{@delimiter}\" & playlist_special\n"\
95
+ " return str\n"\
96
+ " on error\n"\
97
+ " return 0\n"\
98
+ " end try\n"\
99
+ "end tell"\
100
+ end
101
+
102
+ # def get_playlist_tracks_script playlist, offset, size
103
+ # "tell application \"iTunes\"\n"\
104
+ # " try\n"\
105
+ # " set playlist_tracks to every track in playlist \"#{playlist}\"\n"\
106
+ # " if (#{offset} + 1) * #{size} is less than (count of playlist_tracks) then\n"\
107
+ # " set max to (#{offset} + 1) * #{size}\n"\
108
+ # " else\n"\
109
+ # " set max to count of playlist_tracks\n"\
110
+ # " end if\n"\
111
+ # " set min to (#{offset} * #{size}) + 1\n"\
112
+ # " display dialog min\n"\
113
+ # " display dialog max\n"\
114
+ # " set str to \"\"\n"\
115
+ # " repeat with n from min to max\n"\
116
+ # " set data_track to item n of playlist_tracks\n"\
117
+ # " set data_artist to artist of data_track as string\n"\
118
+ # " set data_album to album of data_track as string\n"\
119
+ # " set data_title to name of data_track as string\n"\
120
+ # " set data_id to database ID of data_track as string\n"\
121
+ # " set data_location to POSIX path of (location of data_track as string)\n"\
122
+ # " set str to str & data_artist & \"#{@delimiter}\" "\
123
+ # " & data_album & \"#{@delimiter}\" "\
124
+ # " & data_title & \"#{@delimiter}\" "\
125
+ # " & data_id & \"#{@delimiter}\" "\
126
+ # " & data_location & \"#{@delimiter*2}\n"\
127
+ # " end repeat\n"\
128
+ # " on error\n"\
129
+ # " return 0\n"\
130
+ # " end try\n"\
131
+ # "end tell"
132
+ # end
133
+ def get_playlist_tracks_script playlist
134
+ "tell application \"iTunes\"\n"\
135
+ " try\n"\
136
+ " set playlist_tracks to every track in playlist \"#{playlist}\"\n"\
137
+ " set str to \"\"\n"\
138
+ " repeat with playlist_track in playlist_tracks\n"\
139
+ " set data_location to location of playlist_track as string\n"\
140
+ " if data_location is equal to \"missing value\" then\n"\
141
+ " set data_artist to artist of playlist_track as string\n"\
142
+ " set data_album to album of playlist_track as string\n"\
143
+ " set data_title to name of playlist_track as string\n"\
144
+ " set data_id to database ID of playlist_track as string\n"\
145
+ " set str to str & data_artist & \"#{@delimiter}\" "\
146
+ " & data_album & \"#{@delimiter}\" "\
147
+ " & data_title & \"#{@delimiter}\" "\
148
+ " & data_id & \"#{@delimiter*2}\"\n"\
149
+ " end if\n"\
150
+ " end repeat\n"\
151
+ " return str\n"\
76
152
  " on error\n"\
77
153
  " return 0\n"\
78
154
  " end try\n"\
@@ -83,32 +159,65 @@ module Henchman
83
159
  "osascript -e '#{script}' 2> /dev/null"
84
160
  end
85
161
 
86
- def get_selection ret
162
+ def get_selection
87
163
  selection = %x( #{applescript_command(get_selection_script)} ).chomp
88
- info = selection.split(@delimiter)
89
- if info.empty?
164
+ info = selection.split @delimiter
165
+ track = Hash.new
166
+ if !info.empty?
167
+ track[:artist] = info[0]
168
+ track[:album] = info[1]
169
+ track[:track] = info[2]
170
+ track[:id] = info[3]
171
+ track[:path] = info[4]
172
+ end
173
+ track
174
+ end
175
+
176
+ def get_playlist
177
+ playlist = %x(#{applescript_command(get_playlist_script)}).chomp
178
+ playlist = playlist.split @delimiter
179
+ if playlist[1] != 'none'
90
180
  false
91
- elsif info[3] == "/missing value" || !File.exists?(info[3])
92
- ret[:artist] = info[0]
93
- ret[:album] = info[1]
94
- ret[:track] = info[2]
95
- true
96
181
  else
97
- false
182
+ playlist[0]
183
+ end
184
+ end
185
+
186
+ def get_playlist_tracks playlist
187
+ tracks = Array.new
188
+ tmp_tracks = %x(#{applescript_command(get_playlist_tracks_script playlist)}).chomp
189
+ tmp_tracks = tmp_tracks.split @delimiter*2
190
+ tmp_tracks.each_with_index do |track, index|
191
+ next if track.empty?
192
+ tmp_track = track.split @delimiter
193
+ tracks.push( {:artist => tmp_track[0],
194
+ :album => tmp_track[1],
195
+ :track => tmp_track[2],
196
+ :id => tmp_track[3]} )
98
197
  end
198
+ tracks
99
199
  end
100
200
 
101
201
  def get_album_tracks_of selection
102
202
  artist = selection[:artist]
103
203
  album = selection[:album]
104
- tracks = %x(#{applescript_command(get_album_tracks_script artist, album)}).chomp
105
- tracks = tracks.split(@delimiter)
106
- tracks.delete selection[:track]
204
+ tracks = Array.new
205
+ tmp_tracks = %x(#{applescript_command(get_album_tracks_script artist, album)}).chomp
206
+ tmp_tracks = tmp_tracks.split @delimiter*2
207
+ tmp_tracks.each_with_index do |track, index|
208
+ next if track.empty?
209
+ tmp_track = track.split @delimiter
210
+ next if tmp_track[3] == selection[:id]
211
+ tracks.push( {:artist => tmp_track[0],
212
+ :album => tmp_track[1],
213
+ :track => tmp_track[2],
214
+ :id => tmp_track[3]} )
215
+ end
107
216
  tracks
108
217
  end
109
218
 
110
- def fetch?
111
- (%x(#{applescript_command(prompt_script)}).chomp == "button returned:OK") ? true : false
219
+ def fetch? prompt
220
+ (%x(#{applescript_command(prompt_script prompt)}).chomp == "button returned:OK") ? true : false
112
221
  end
113
222
 
114
223
  def get_active_app
@@ -116,7 +225,7 @@ module Henchman
116
225
  end
117
226
 
118
227
  def set_track_location selection, local_file
119
- ret = %x(#{applescript_command(update_track_location_script selection, local_file)}).chomp
228
+ ret = %x(#{applescript_command(update_track_location_script selection[:id], local_file)}).chomp
120
229
  if ret.empty? || ret == '0'
121
230
  puts "Could not update location of #{selection.values.join(':')} to #{local_file}"
122
231
  false
data/lib/core.rb CHANGED
@@ -12,11 +12,13 @@ module Henchman
12
12
  begin
13
13
  cache_file = File.expand_path("~/.henchman/cache")
14
14
  @ignore = YAML.load_file(cache_file)
15
+ raise "Incorrectly formatted cache" if !(@ignore.include? :artist)
16
+
17
+ @ignore.each_value { |val| val.default = 0 }
15
18
  rescue StandardError => err
16
19
  puts "Error opening cache file (#{err})"
17
- @ignore = Hash.new
20
+ @ignore = Henchman::Templates.cache
18
21
  end
19
- @ignore.default = 0
20
22
 
21
23
  threads = []
22
24
  update_cache = false
@@ -37,40 +39,50 @@ module Henchman
37
39
  return
38
40
  end
39
41
 
40
- selection = Hash.new
41
- track_selected = @appleScript.get_selection selection
42
-
43
- if track_selected && @ignore[selection[:artist]] < (Time.now.to_i - @config[:reprompt_timeout])
44
- update_cache = true
45
- @ignore.delete selection[:artist]
46
- if @appleScript.fetch?
47
- puts "searching"
48
- begin
49
- # first download the selected track
50
- dropbox_path = @dropbox.search_for selection
51
- file_save_path = @dropbox.download selection, dropbox_path
52
-
53
- # if we downloaded it, update the location of the track in iTunes
54
- unless !file_save_path
55
- updated = @appleScript.set_track_location selection, file_save_path
56
- # if the update failed, cleanup that directory and don't bother
57
- # doing the rest of the album
58
- if !updated
59
- cleanup file_save_path
60
- next
42
+ track = @appleScript.get_selection
43
+
44
+ if track_selected? track
45
+ if (missing_track_selected? track) && !(ignore? :artist, track[:artist])
46
+ update_cache = true
47
+ update_ignore :artist, track[:artist]
48
+ if @appleScript.fetch? "#{track[:album]} by #{track[:artist]}"
49
+ begin
50
+ # first download the selected track
51
+ dropbox_path = @dropbox.search_for track
52
+ file_save_path = @dropbox.download track, dropbox_path
53
+
54
+ # if we downloaded it, update the location of the track in iTunes
55
+ unless !file_save_path
56
+ updated = @appleScript.set_track_location track, file_save_path
57
+ # if the update failed, cleanup that directory and don't bother
58
+ # doing the rest of the album
59
+ if !updated
60
+ cleanup file_save_path
61
+ next
62
+ end
63
+
64
+ # now that we've gotten the selected track, spawn off another process
65
+ # to download the rest of the tracks on the album - spatial locality FTW
66
+ album_tracks = @appleScript.get_album_tracks_of track
67
+ threads << Thread.new{ download_tracks album_tracks }
61
68
  end
62
-
63
- # now that we've gotten the selected track, spawn off another process
64
- # to download the rest of the tracks on the album - spatial locality FTW
65
- album_tracks = @appleScript.get_album_tracks_of selection
66
- threads << Thread.new{ download_album_tracks selection, album_tracks }
69
+ rescue StandardError => err
70
+ puts err
71
+ next
72
+ end
73
+ end
74
+ end
75
+ else
76
+ playlist = @appleScript.get_playlist
77
+ if playlist
78
+ playlist_tracks = @appleScript.get_playlist_tracks playlist
79
+ if (!playlist_tracks.empty?) && !(ignore? :playlist, playlist)
80
+ update_cache = true
81
+ update_ignore :playlist, playlist
82
+ if @appleScript.fetch? playlist
83
+ threads << Thread.new{ download_tracks playlist_tracks }
67
84
  end
68
- rescue StandardError => err
69
- puts err
70
- next
71
85
  end
72
- else
73
- @ignore[selection[:artist]] = Time.now.to_i
74
86
  end
75
87
  end
76
88
  sleep @config[:poll_track]
@@ -80,31 +92,56 @@ module Henchman
80
92
  File.open(cache_file, "w") { |f| f.write( @ignore.to_yaml ) } if update_cache
81
93
  end
82
94
 
95
+ def self.update_ignore type, identifier
96
+ return false if !(valid_ignore_type? type)
97
+ @ignore[type][identifier] = Time.now.to_i
98
+ end
99
+
100
+ def self.ignore? type, identifier
101
+ return false if !(valid_ignore_type? type)
102
+ @ignore[type][identifier] >= (Time.now.to_i - @config[:reprompt_timeout])
103
+ end
104
+
105
+ def self.valid_ignore_type? type
106
+ if !(Henchman::Templates.cache.keys.include? type)
107
+ puts "Invalid type '#{type}' for ignore cache check"
108
+ false
109
+ else
110
+ true
111
+ end
112
+ end
113
+
114
+ def self.track_selected? track
115
+ !track.empty?
116
+ end
117
+
118
+ def self.missing_track_selected? track
119
+ track[:path] == '/missing value'
120
+ end
121
+
83
122
  def self.itunes_is_active?
84
123
  @appleScript.get_active_app == 'iTunes'
85
124
  end
86
125
 
87
- def self.download_album_tracks selection, album_tracks
88
- album_tracks.each do |album_track|
89
- selection[:track] = album_track
90
- begin
91
- # first download the selected track
92
- dropbox_path = @dropbox.search_for selection
93
- file_save_path = @dropbox.download selection, dropbox_path
94
-
95
- # if we downloaded it, update the location of the track in iTunes
96
- unless !file_save_path
97
- updated = @appleScript.set_track_location selection, file_save_path
98
- # if the update failed, remove that file
99
- if !updated
100
- cleanup file_save_path
101
- next
102
- end
103
- end
104
- rescue StandardError => err
105
- puts err
106
- next
126
+ def self.download_tracks album_tracks
127
+ album_tracks.each { |album_track| download_and_update album_track }
128
+ end
129
+
130
+ def self.download_and_update track
131
+ begin
132
+ # first download the selected track
133
+ dropbox_path = @dropbox.search_for track
134
+ file_save_path = @dropbox.download track, dropbox_path
135
+
136
+ # if we downloaded it, update the location of the track in iTunes
137
+ unless !file_save_path
138
+ updated = @appleScript.set_track_location track, file_save_path
139
+
140
+ # if the update failed, remove that file
141
+ cleanup file_save_path if !updated
107
142
  end
143
+ rescue StandardError => err
144
+ puts err
108
145
  end
109
146
  end
110
147
 
data/lib/dropbox.rb CHANGED
@@ -44,7 +44,11 @@ module Henchman
44
44
 
45
45
  def search_for selection
46
46
  # search Dropbox for the file
47
- results = @client.search(@config[:dropbox][:root], selection[:track])
47
+ begin
48
+ results = @client.search(@config[:dropbox][:root], selection[:track])
49
+ rescue DropboxError => msg
50
+ raise "Error accessing Dropbox Search API on #{selection.values.join(':')}: #{msg}"
51
+ end
48
52
 
49
53
  # get rid of any results that are directories
50
54
  results.reject! { |result| result['is_dir'] }
@@ -1,3 +1,3 @@
1
1
  module Henchman
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -20,7 +20,7 @@ module Henchman
20
20
  cache_path = File.expand_path("~/.henchman/cache")
21
21
  File.write(plist_path, plist)
22
22
  File.write(shell_script_path, Henchman::Templates.shell_script)
23
- File.open(cache_path, "w") { |f| f.write( {}.to_yaml ) }
23
+ File.open(cache_path, "w") { |f| f.write( Henchman::Templates.cache.to_yaml ) }
24
24
 
25
25
  puts "Launching agent"
26
26
  `chmod +x #{shell_script_path}`
data/lib/templates.rb CHANGED
@@ -30,18 +30,25 @@ module Henchman
30
30
  end
31
31
 
32
32
  def self.config
33
- yml = {
34
- :dropbox => {:key => '',
35
- :secret => '',
36
- :access_token => '',
37
- :user_id => '',
38
- :root => ''},
39
- :root => '',
40
- :poll_itunes_open => 10,
41
- :poll_track => 3,
42
- :reprompt_timeout => 300,
43
- :delimiter => '|~|'
44
- }
33
+ {
34
+ :dropbox => {:key => '',
35
+ :secret => '',
36
+ :access_token => '',
37
+ :user_id => '',
38
+ :root => ''},
39
+ :root => '',
40
+ :poll_itunes_open => 10,
41
+ :poll_track => 3,
42
+ :reprompt_timeout => 300,
43
+ :delimiter => '|~|'
44
+ }
45
+ end
46
+
47
+ def self.cache
48
+ {
49
+ :artist => Hash.new(0),
50
+ :playlist => Hash.new(0)
51
+ }
45
52
  end
46
53
  end
47
54
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: henchman-sync
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Greg Merritt
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-06-27 00:00:00.000000000 Z
11
+ date: 2016-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: commander