shroom 0.0.9 → 0.0.10

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.
@@ -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