shroom 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,7 +7,7 @@ require 'sh_artist'
7
7
  module Sh
8
8
  class Database
9
9
  def initialize
10
- @db = Sequel.sqlite("#{$config_dir}/songs.db")
10
+ @db = Sequel.sqlite(Sh::Global::PATHS[:db_file])
11
11
 
12
12
  @db.create_table :artists do
13
13
  primary_key :id
@@ -117,7 +117,7 @@ module Sh
117
117
 
118
118
  return row_song
119
119
  rescue
120
- puts "Couldn's save song in database: " + song.path
120
+ Sh::Log.warning "Couldn't save #{song.path} to database", $!
121
121
  return
122
122
  end
123
123
  end
@@ -163,7 +163,7 @@ module Sh
163
163
  @db[:songs].each do |hash|
164
164
  path = hash[:path]
165
165
  unless File.exists? path
166
- puts "Removing: #{path}"
166
+ Sh::Log.debug "Removing #{path} from database"
167
167
  @db[:songs].filter(:path => path).delete
168
168
  end
169
169
  end
@@ -204,4 +204,4 @@ module Sh
204
204
  return artist
205
205
  end
206
206
  end
207
- end
207
+ end
@@ -2,6 +2,7 @@ require 'fileutils'
2
2
  require 'yaml'
3
3
  require 'libglade2'
4
4
  require 'ping'
5
+ require 'gtk2'
5
6
  require 'sh_plugin'
6
7
 
7
8
  module Sh
@@ -13,31 +14,44 @@ module Sh
13
14
  class Global
14
15
  SUPPORTED_EXTENSIONS = ['.mp3', '.m4a', '.ogg', '.flac', '.wma', '.wav']
15
16
  GLADE = {}
17
+ PATHS = {}
16
18
 
17
19
  def self.init
18
- # Won't work on Windows
19
- $home = ENV['HOME']
20
- $config_dir = "#{$home}/.shroom"
21
- FileUtils.mkdir $config_dir unless File.exists? $config_dir
22
- $cover_dir = "#{$config_dir}/covers"
23
- FileUtils.mkdir $cover_dir unless File.exists? $cover_dir
24
- @@prefs_file = "#{$config_dir}/preferences.yml"
20
+ # Setup some commonly used directories
21
+ PATHS[:home_dir] = home_dir = GLib.home_dir
22
+ PATHS[:config_dir] = config_dir = "#{home_dir}/.shroom"
23
+ FileUtils.mkdir config_dir unless File.exists? config_dir
24
+ PATHS[:cover_dir] = cover_dir = "#{config_dir}/covers"
25
+ FileUtils.mkdir cover_dir unless File.exists? cover_dir
26
+ # Store paths to relevant Shroom files
27
+ PATHS[:prefs_file] = "#{config_dir}/preferences.yml"
28
+ PATHS[:plugin_prefs_file] = "#{config_dir}/plugin_prefs.yml"
29
+ PATHS[:db_file] = "#{config_dir}/songs.db"
30
+ PATHS[:log_file] = log_file = "#{config_dir}/shroom.log"
31
+ PATHS[:playlists_file] = "#{config_dir}/playlists.yml"
25
32
 
33
+ # Set default preferences
26
34
  @@prefs = {
27
- :library_dir => "#{$home}/Music",
35
+ :library_dir => "#{home_dir}/Music",
28
36
  :cover_art => true,
29
- :lyrics => true,
30
- :lastfm => false,
31
- :lastfm_user => nil,
32
- :lastfm_password => nil,
37
+ :lyrics => true
33
38
  }
39
+
40
+ # Load saved preferences
41
+ load_prefs
42
+
43
+ # Set log outputs
44
+ Sh::Log.add_output_stream :stdout
45
+ Sh::Log.add_output_stream open(log_file, "w")
34
46
 
47
+ # Parse Glade interface
35
48
  add_toplevel_glade('dlg_preferences')
36
49
  add_toplevel_glade('dlg_song')
37
50
  add_toplevel_glade('dlg_plugins')
38
51
  GLADE.freeze
39
52
 
40
- load_prefs
53
+ # Initialize plugins
54
+ Plugin.init
41
55
  load_plugins
42
56
  end
43
57
 
@@ -49,30 +63,32 @@ module Sh
49
63
  return @@prefs
50
64
  end
51
65
 
66
+ # Set current preferences to those in the preferences file
52
67
  def self.load_prefs
53
- if File.exists?(@@prefs_file)
54
- prefs = YAML::load(File.read(@@prefs_file))
68
+ if File.exists?(PATHS[:prefs_file])
69
+ prefs = YAML::load(File.read(PATHS[:prefs_file]))
55
70
  prefs.each do |k, v|
56
71
  @@prefs[k] = v
57
72
  end
58
73
  end
59
74
  end
60
-
75
+
76
+ # Store current preferences in the preferences file
61
77
  def self.save_prefs
62
- File.open(@@prefs_file, 'w') do |f|
78
+ File.open(PATHS[:prefs_file], 'w') do |f|
63
79
  f.write @@prefs.to_yaml
64
80
  end
65
81
  end
66
-
82
+
83
+ # Search for Shroom plugins and load them
67
84
  def self.load_plugins
68
- plugin_dirs = ["#{self.locate("plugins")}", "#{$config_dir}/.plugins"]
85
+ plugin_dirs = ["#{self.locate("plugins")}", "#{PATHS[:config_dir]}/.plugins"]
69
86
  plugin_dirs.each do |dir|
70
87
  Dir["#{dir}/*.rb"].each do |rb_file|
71
88
  begin
72
89
  require rb_file
73
90
  rescue Exception
74
- puts "Error loading plugin: #{rb_file}"
75
- puts $!
91
+ Sh::Log.warning "Error loading plugin \"#{rb_file}\"", $!
76
92
  end
77
93
  end
78
94
  end
@@ -80,9 +96,10 @@ module Sh
80
96
 
81
97
  private
82
98
  def self.add_toplevel_glade name
99
+ # Parse and store toplevel window from Glade file
83
100
  GLADE[name] = GladeXML.new(Global.locate('shroom.glade'), name)
84
101
  end
85
102
 
86
103
  init()
87
104
  end
88
- end
105
+ end
@@ -0,0 +1,78 @@
1
+ module Sh
2
+ class Log
3
+ class Broadcaster
4
+ def initialize type
5
+ @type = type
6
+ end
7
+
8
+ def write str
9
+ @buffer ||= ''
10
+ @buffer << str
11
+ if @buffer.end_with? "\n"
12
+ case @type
13
+ when :stderr
14
+ Log.error '> ' + @buffer
15
+ when :stdout
16
+ Log.info '> ' + @buffer
17
+ end
18
+ @buffer = nil
19
+ end
20
+ end
21
+
22
+ def puts str
23
+ write str + "\n"
24
+ end
25
+ end
26
+
27
+ LEVELS = [:debug, :info, :warning, :error]
28
+ @@output_streams = []
29
+ @@stdout = $stdout
30
+ @@stderr = $stderr
31
+ $stdout = Broadcaster.new :stdout
32
+ $stderr = Broadcaster.new :stderr
33
+
34
+ def self.add_output_stream stream, level=:debug
35
+ case stream
36
+ when :stdout
37
+ stream = @@stdout
38
+ when :stderr
39
+ stream = @@stderr
40
+ end
41
+ @@output_streams << [stream, level]
42
+ end
43
+
44
+ def self.debug(message, error=nil)
45
+ log message, :debug, error
46
+ end
47
+
48
+ def self.info(message, error=nil)
49
+ log message, :info, error
50
+ end
51
+
52
+ def self.warning(message, error=nil)
53
+ log message, :warning, error
54
+ end
55
+
56
+ def self.error(message, error=nil)
57
+ log message, :error, error
58
+ end
59
+
60
+ private
61
+ def self.log(message, type=:info, error=nil)
62
+ string = "[#{type.to_s.upcase}]"
63
+ string << " " * (10 - string.length)
64
+ string << message
65
+ if error
66
+ string << '> '
67
+ string << error
68
+ end
69
+ time = Time.now.to_a.reverse[-3..-1].collect {|e| "%.2d" % e}
70
+ @@output_streams.each do |stream, level|
71
+ if LEVELS.index(type) >= LEVELS.index(level)
72
+ stream.puts "(#{time.join ':'}) #{string}"
73
+ stream.flush
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -1,68 +1,15 @@
1
- require 'open-uri'
2
- require 'hpricot'
3
- require 'cgi'
4
- require 'ping'
5
-
6
1
  module Sh
7
2
  class Lyrics
8
3
  def Lyrics.get_lyrics song
9
4
  artist, title = song.artist.name, song.title
10
5
  lyrics = nil
11
6
  if artist and title
12
- url = "http://lyrical.heroku.com/api?format=plain"
13
- url << "&artist=#{CGI.escape(artist)}"
14
- url << "&song=#{CGI.escape(title)}"
15
- lyrics = open(url).read
7
+ @lyrical_rest ||= Rest::Get.new("http://lyrical.heroku.com/api", :format => 'plain')
8
+ lyrics = @lyrical_rest[:artist => artist, :song => title]
16
9
  lyrics = nil if lyrics.empty?
17
10
  end
18
11
  return lyrics
19
12
  end
20
-
21
- # class LyricsWiki
22
- # def LyricsWiki.get_lyrics(artist, title)
23
- # lyrics = nil
24
- # if Ping.pingecho("lyricwiki.org", 10)
25
- # url = "http://lyricwiki.org/api.php?func=getSong&fmt=xml" +
26
- # "&artist=#{CGI.escape(artist)}" +
27
- # "&song=#{CGI.escape(title)}"
28
- # doc = Hpricot(open(url))
29
- # page_url = (doc/'url').inner_text
30
- # page_name = (page_url.match(/:([^:]*)$/) || ":").to_s[1..-1]
31
- # page_name = CGI.unescape(page_name)
32
- # page_name.freeze
33
- # unless page_url.end_with? ';action=edit'
34
- # doc = Hpricot(open(page_url).read.gsub(/<[\s]*br[\s]*\/?>/, "\n"))
35
- # versions = []
36
- # lyricboxes = doc/'div.lyricbox'
37
- # best_match = [0, lyricboxes.first.inner_text.strip]
38
- # if lyricboxes.size > 1 and StringMatcher.compare_ignore_case(page_name, title) < 0.9
39
- # lyricboxes.each do |box|
40
- # prev = box.previous
41
- # version = nil
42
- # while prev
43
- # unless (prev/'.mw-headline').empty?
44
- # version = prev.inner_text.strip
45
- # break
46
- # end
47
- # prev = prev.previous
48
- # end
49
- # version.sub! /^\[edit\] /, ''
50
- # lyrics = box.inner_text.strip
51
- # versions << [version, lyrics]
52
- # end
53
- # versions.each do |version, lyrics|
54
- # version_title = page_name.dup
55
- # version_title << " (#{version})" if version
56
- # match = [StringMatcher.compare_ignore_case(version_title, title), lyrics]
57
- # best_match = match if match.first > best_match.first
58
- # end
59
- # end
60
- # lyrics = best_match[1]
61
- # end
62
- # end
63
- # return lyrics
64
- # end
65
- # end
66
13
  end
67
14
  end
68
15
 
@@ -1,11 +1,10 @@
1
1
  #!/usr/bin/ruby
2
2
 
3
- # Note to self (Mercurial push URL):
4
- # http://dismal_denizen@bitbucket.org/dismal_denizen/shroom/
5
-
6
3
  require 'socket'
4
+ require 'rubygems'
7
5
 
8
6
  require 'sh_util'
7
+ require 'sh_log'
9
8
  require 'sh_global'
10
9
  require 'sh_song'
11
10
  require 'sh_database'
@@ -15,6 +14,8 @@ require 'sh_view'
15
14
  module Sh
16
15
  class Main
17
16
  def initialize
17
+ Sh::Log.info 'Shroom started'
18
+
18
19
  # Load the database
19
20
  $db = Sh::Database.new
20
21
 
@@ -23,12 +24,15 @@ module Sh
23
24
  Sh::View.new.show
24
25
  end
25
26
 
26
- puts 'Saving preferences...'
27
+ Sh::Log.debug 'Saving preferences...'
27
28
  Sh::Global.save_prefs
28
- puts 'Bye!'
29
+ Sh::Log.debug 'Global preferences saved'
30
+ Plugin.save_prefs
31
+ Sh::Log.debug 'Plugin preferences saved'
32
+ Sh::Log.info 'Shroom quit'
29
33
  end
30
34
  end
31
35
  end
32
36
 
33
37
  # Get the show started if Shroom is run as a standalone application
34
- Sh::Main.new if $0 == __FILE__
38
+ Sh::Main.new if $0 == __FILE__
@@ -1,6 +1,4 @@
1
1
  require 'wx'
2
- require 'rubygems'
3
- require 'scrobbler'
4
2
  require 'sh_plugin'
5
3
 
6
4
  # I believe that a small explanation is in order. You may be wondering why Wx
@@ -127,7 +125,7 @@ module Sh
127
125
  # Load a media file into the MediaCtrl
128
126
  def load_file(path)
129
127
  unless @mc.load(path)
130
- puts "Unable to load file into MediaCtrl"
128
+ Sh::Log.warning "Unable to load #{path} into MediaCtrl"
131
129
  on_media_finished nil
132
130
  end
133
131
  end
@@ -169,7 +167,7 @@ module Sh
169
167
  # Start the playback
170
168
  def play
171
169
  unless @mc.play
172
- puts "Unable to play media with MediaCtrl"
170
+ Sh::Log.warning "Unable to play song with MediaCtrl"
173
171
  on_media_finished nil
174
172
  end
175
173
  end
@@ -1,23 +1,26 @@
1
1
  module Sh
2
2
  class Playlist
3
- def initialize dir
4
- @dir = dir
3
+ attr_accessor :name
4
+
5
+ def initialize name
6
+ @name = name
5
7
  @songs = []
6
8
  end
7
9
 
8
- def Playlist.parse(path)
9
- list = Playlist.new(File.dirname(path))
10
+ def self.parse(path)
11
+ list = Playlist.new path.match(/\/([^\/]*)$/).captures.last
10
12
  open(path) do |f|
11
13
  while line = f.gets
12
14
  line.strip!
13
15
  unless line[0] == ?# or line.empty?
16
+ # TODO: make cross-platform
14
17
  ref = File.expand_path(line.gsub('\\', '/'), File.dirname(path))
15
18
  if File.exists? ref
16
19
  if Sh::Global::SUPPORTED_EXTENSIONS.include? File.extname(ref)
17
20
  list << ($db.songs(:path => ref).first || Sh::Song.new(ref).read_tags!)
18
21
  end
19
22
  else
20
- puts "Not found: " + ref
23
+ Sh::Log.info "File at #{ref} not found"
21
24
  end
22
25
  end
23
26
  end
@@ -32,5 +35,87 @@ module Sh
32
35
  def songs
33
36
  return @songs.dup
34
37
  end
38
+
39
+ def save!
40
+ path = Sh::Global::PATHS[:playlists_file]
41
+ playlists = []
42
+ if File.exists? path
43
+ playlists = YAML.load_file path
44
+ end
45
+ playlists.reject! {|name, paths| name == @name}
46
+ song_paths = @songs.map {|song| song.path}
47
+ playlists << [@name, song_paths]
48
+ open(path, 'w') {|f| f.puts playlists.to_yaml}
49
+ end
50
+ end
51
+
52
+ class Playlists
53
+ def initialize view
54
+ @tree = TreeView.new
55
+ @tree.selection.mode = Gtk::SELECTION_MULTIPLE
56
+
57
+ renderer = CellRendererText.new
58
+ column = TreeViewColumn.new('Playlists',
59
+ renderer,
60
+ 'text' => 0)
61
+ @tree.append_column column
62
+
63
+ @model = TreeStore.new(String, Array)
64
+ @tree.model = @model
65
+
66
+ @tree.signal_connect('row_activated') do |widget, path, col|
67
+ iter = @tree.model.get_iter(path)
68
+ view.stop
69
+ view.queue = iter[1].dup
70
+ view.play
71
+ end
72
+
73
+ @scroll = ScrolledWindow.new(nil, nil)
74
+ @scroll.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
75
+ @scroll.add @tree
76
+ end
77
+
78
+ def self.load_playlists
79
+ playlists = []
80
+ if File.exists? Sh::Global::PATHS[:playlists_file]
81
+ arr = YAML.load_file Sh::Global::PATHS[:playlists_file]
82
+ arr.sort_by {|a| (a.first || '')}.each do |name, paths|
83
+ playlist = Playlist.new name
84
+ paths.each do |path|
85
+ if File.exists? path
86
+ if Sh::Global::SUPPORTED_EXTENSIONS.include? File.extname(path)
87
+ song = ($db.songs(:path => path).first || Sh::Song.new(path).read_tags!)
88
+ playlist << song
89
+ end
90
+ else
91
+ Sh::Log.info "File at #{ref} not found"
92
+ end
93
+ end
94
+ playlists << playlist
95
+ end
96
+ end
97
+ return playlists
98
+ end
99
+
100
+ def fill_model
101
+ @model.clear
102
+ Playlists.load_playlists.each do |playlist|
103
+ parent_node = @model.append nil
104
+ parent_node[0] = playlist.name
105
+ parent_node[1] = []
106
+ playlist.songs.each do |song|
107
+ node = @model.append parent_node
108
+ node[0] = song.title
109
+ node[1] = [song]
110
+ parent_node[1] << song
111
+ end
112
+ Kelp.process_events
113
+ end
114
+ end
115
+
116
+ def widget
117
+ fill_model
118
+ return @scroll
119
+ end
35
120
  end
36
- end
121
+ end