dhun 0.5.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.
- data/LICENSE.txt +22 -0
- data/README.md +32 -0
- data/Rakefile +83 -0
- data/TODO.md +19 -0
- data/bin/dhun +7 -0
- data/ext/Makefile +157 -0
- data/ext/dhun.h +37 -0
- data/ext/dhun_ext.c +84 -0
- data/ext/extconf.rb +15 -0
- data/ext/player.c +187 -0
- data/ext/query.c +102 -0
- data/lib/dhun.rb +13 -0
- data/lib/dhun/command.rb +15 -0
- data/lib/dhun/controller.rb +116 -0
- data/lib/dhun/dhun_client.rb +30 -0
- data/lib/dhun/dhun_server.rb +43 -0
- data/lib/dhun/handler.rb +80 -0
- data/lib/dhun/player.rb +66 -0
- data/lib/dhun/query.rb +44 -0
- data/lib/dhun/result.rb +32 -0
- data/lib/dhun/runner.rb +87 -0
- data/lib/dhun/server.rb +39 -0
- metadata +76 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'json'
|
3
|
+
module Dhun
|
4
|
+
class DhunClient
|
5
|
+
def initialize(options)
|
6
|
+
@options = options
|
7
|
+
@socket = options[:socket]
|
8
|
+
unless DhunClient.is_dhun_server_running?(@socket)
|
9
|
+
raise "Dhun server is not running"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def send(message)
|
14
|
+
u = UNIXSocket.new(@socket)
|
15
|
+
u.puts message
|
16
|
+
resp = u.read
|
17
|
+
u.close
|
18
|
+
return resp
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.is_dhun_server_running?(socket)
|
22
|
+
begin
|
23
|
+
u = UNIXSocket.new(socket)
|
24
|
+
return true
|
25
|
+
rescue StandardError => ex
|
26
|
+
return false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'json'
|
2
|
+
module Dhun
|
3
|
+
# Handler for commands sent from client
|
4
|
+
module DhunServer
|
5
|
+
def post_init
|
6
|
+
#puts "-- client connected"
|
7
|
+
end
|
8
|
+
|
9
|
+
def receive_data data
|
10
|
+
begin
|
11
|
+
puts data
|
12
|
+
cmd = JSON.parse(data)
|
13
|
+
@command = cmd["command"]
|
14
|
+
@arguments = cmd["arguments"]
|
15
|
+
handle_client_request
|
16
|
+
rescue StandardError => ex
|
17
|
+
puts "Error parsing command : #{ex.message}"
|
18
|
+
puts ex.backtrace
|
19
|
+
ensure
|
20
|
+
close_connection true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def handle_client_request
|
25
|
+
handler = Handler.new
|
26
|
+
begin
|
27
|
+
if @command.nil?
|
28
|
+
raise "Command Not Found"
|
29
|
+
end
|
30
|
+
result = handler.send(@command,*@arguments)
|
31
|
+
puts "Sending #{result}"
|
32
|
+
send_data result
|
33
|
+
rescue StandardError => ex
|
34
|
+
puts "-- error : #{ex.message}"
|
35
|
+
puts ex.backtrace
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def unbind
|
40
|
+
#puts "-- client disconnected"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/dhun/handler.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'json'
|
2
|
+
module Dhun
|
3
|
+
# Handling commands sent by Dhun client
|
4
|
+
class Handler
|
5
|
+
def stop
|
6
|
+
result = Result.new :success, "Dhun is stopping"
|
7
|
+
Server.stop
|
8
|
+
Player.instance.pause
|
9
|
+
return result.to_json
|
10
|
+
end
|
11
|
+
|
12
|
+
def play(*args)
|
13
|
+
@player = Player.instance
|
14
|
+
q = Query.new(args.join(" "))
|
15
|
+
if q.is_valid?
|
16
|
+
files = q.execute_spotlight_query
|
17
|
+
if files.empty?
|
18
|
+
result = Result.new :error, "No Results Found"
|
19
|
+
else
|
20
|
+
@player.play_files files
|
21
|
+
result = Result.new :success, "#{files.size} files queued for playing",
|
22
|
+
:files => files
|
23
|
+
end
|
24
|
+
else
|
25
|
+
result = Result.new :error, "Invalid query syntax. See dhun -h for correct syntax"
|
26
|
+
end
|
27
|
+
result.to_json
|
28
|
+
end
|
29
|
+
|
30
|
+
def enqueue(*args)
|
31
|
+
@player = Player.instance
|
32
|
+
q = Query.new(args.join(" "))
|
33
|
+
if q.is_valid?
|
34
|
+
files = q.execute_spotlight_query
|
35
|
+
if files.empty?
|
36
|
+
result = Result.new :error, "No Results Found"
|
37
|
+
else
|
38
|
+
@player.enqueue files
|
39
|
+
result = Result.new :success, "#{files.size} files queued for playing.",
|
40
|
+
:files => files
|
41
|
+
end
|
42
|
+
else
|
43
|
+
result = Result.new :error, "Invalid query syntax. See dhun -h for correct syntax"
|
44
|
+
end
|
45
|
+
result.to_json
|
46
|
+
end
|
47
|
+
|
48
|
+
def status
|
49
|
+
@player = Player.instance
|
50
|
+
status_msg = (@player.status == :playing) ? "Dhun is running" : "Dhun is paused"
|
51
|
+
now_playing = @player.current
|
52
|
+
queue = @player.queue
|
53
|
+
result = Result.new :success, status_msg, :now_playing => now_playing, :queue => queue
|
54
|
+
result.to_json
|
55
|
+
end
|
56
|
+
|
57
|
+
def next(*args)
|
58
|
+
@player = Player.instance
|
59
|
+
next_track = @player.next
|
60
|
+
result = Result.new :success, (next_track || "No More Tracks")
|
61
|
+
return result.to_json
|
62
|
+
end
|
63
|
+
|
64
|
+
def pause
|
65
|
+
@player = Player.instance
|
66
|
+
@player.stop
|
67
|
+
track = @player.queue.first
|
68
|
+
result = Result.new :success, "Dhun is paused. " + (track ? "Next track is #{track}" : "No more tracks in queue.")
|
69
|
+
return result.to_json
|
70
|
+
end
|
71
|
+
|
72
|
+
def resume
|
73
|
+
@player = Player.instance
|
74
|
+
track = @player.queue.first
|
75
|
+
@player.play
|
76
|
+
result = Result.new :success, (track ? "Dhun is playing. Next track is #{track}" : "No more tracks in queue.")
|
77
|
+
return result.to_json
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/dhun/player.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'dhun_ext'
|
3
|
+
module Dhun
|
4
|
+
class Player
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
attr_reader :queue
|
8
|
+
attr_reader :status
|
9
|
+
attr_reader :current
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@queue = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def empty_queue
|
16
|
+
stop
|
17
|
+
@queue.clear
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def play_files(files)
|
22
|
+
if files.empty?
|
23
|
+
puts "Empty List"
|
24
|
+
else
|
25
|
+
stop
|
26
|
+
empty_queue
|
27
|
+
files.each { |f| self.queue.push f }
|
28
|
+
play
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def enqueue(files)
|
33
|
+
files.each { |f| self.queue.push f }
|
34
|
+
play
|
35
|
+
end
|
36
|
+
|
37
|
+
def play
|
38
|
+
return if @status == :playing
|
39
|
+
@status = :playing
|
40
|
+
@player_thread = Thread.new do
|
41
|
+
while @status == :playing and !queue.empty?
|
42
|
+
@current = @queue.shift
|
43
|
+
puts "playing #{@current}"
|
44
|
+
DhunExt.play_file @current
|
45
|
+
end
|
46
|
+
@status = :stopped
|
47
|
+
puts "Player is stopped"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def stop
|
52
|
+
@status = :stopped
|
53
|
+
@current = nil
|
54
|
+
DhunExt.pause_play
|
55
|
+
# Wait for @player_thread to exit cleanly
|
56
|
+
@player_thread.join unless @player_thread.nil?
|
57
|
+
end
|
58
|
+
|
59
|
+
def next
|
60
|
+
stop # stops current track
|
61
|
+
next_track = @queue.first
|
62
|
+
play # start playing with the next track
|
63
|
+
return next_track
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/dhun/query.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'dhun_ext'
|
2
|
+
module Dhun
|
3
|
+
class Query
|
4
|
+
|
5
|
+
MD_ITEMS = [:kMDItemAlbum, :kMDItemAuthors, :kMDItemComposer, :kMDItemDisplayName, :kMDItemFSName, :kMDItemTitle, :kMDItemMusicalGenre]
|
6
|
+
attr_reader :query_string
|
7
|
+
attr_reader :spotlight_query
|
8
|
+
|
9
|
+
def initialize(query_string)
|
10
|
+
@query_string = query_string
|
11
|
+
parse
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse
|
15
|
+
str = MD_ITEMS.collect do |item|
|
16
|
+
"#{item.to_s} == '#{@query_string}'wc"
|
17
|
+
end.join(" || ")
|
18
|
+
|
19
|
+
@spotlight_query = "kMDItemContentTypeTree == 'public.audio' && (#{str})"
|
20
|
+
#puts @spotlight_query
|
21
|
+
@is_valid = true
|
22
|
+
end
|
23
|
+
|
24
|
+
def is_valid?
|
25
|
+
@is_valid
|
26
|
+
end
|
27
|
+
|
28
|
+
# Use extension to query spotlight
|
29
|
+
def execute_spotlight_query
|
30
|
+
return DhunExt.query_spotlight(@spotlight_query)
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_metadata_item(field)
|
34
|
+
case field
|
35
|
+
when "album" then :kMDItemAlbum
|
36
|
+
when "artist" then :kMDItemAuthors
|
37
|
+
when "composer" then :kMDItemComposer
|
38
|
+
when "title" then :kMDItemTitle
|
39
|
+
when "genre" then :kMDItemMusicalGenre
|
40
|
+
else "Unknown"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/dhun/result.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'json'
|
2
|
+
module Dhun
|
3
|
+
class Result
|
4
|
+
|
5
|
+
def initialize(result, message, options = {})
|
6
|
+
@response = { :result => result,:message => message}
|
7
|
+
@response.merge!(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def success?
|
11
|
+
@response[:result].to_sym == :success
|
12
|
+
end
|
13
|
+
|
14
|
+
def error?
|
15
|
+
@response[:result].to_sym == :error
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](sym)
|
19
|
+
@response[sym] || @response[sym.to_s]
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_json
|
23
|
+
@response.to_json
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.from_json_str(resp_json)
|
27
|
+
resp = JSON.parse(resp_json)
|
28
|
+
Result.new(resp["result"],resp["message"],resp)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
data/lib/dhun/runner.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Dhun
|
4
|
+
|
5
|
+
# Heavily lifted from Thin codebase
|
6
|
+
class Runner
|
7
|
+
COMMANDS = %w(start query)
|
8
|
+
CLIENT_COMMANDS = %w(stop play pause resume next enqueue status)
|
9
|
+
# Parsed options
|
10
|
+
attr_accessor :options
|
11
|
+
|
12
|
+
# Name of the command to be runned.
|
13
|
+
attr_accessor :command
|
14
|
+
|
15
|
+
# Arguments to be passed to the command.
|
16
|
+
attr_accessor :arguments
|
17
|
+
|
18
|
+
# Return all available commands
|
19
|
+
def self.commands
|
20
|
+
commands = COMMANDS + CLIENT_COMMANDS
|
21
|
+
commands
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(argv)
|
25
|
+
@argv = argv
|
26
|
+
# Default options values
|
27
|
+
@options = {
|
28
|
+
:socket => "/tmp/dhun.sock",
|
29
|
+
:pid => 'tmp/pids/dhun.pid',
|
30
|
+
}
|
31
|
+
parse!
|
32
|
+
end
|
33
|
+
|
34
|
+
def parser
|
35
|
+
# NOTE: If you add an option here make sure the key in the +options+ hash is the
|
36
|
+
# same as the name of the command line option.
|
37
|
+
# +option+ keys are used to build the command line to launch other processes,
|
38
|
+
# see <tt>lib/dhun/command.rb</tt>.
|
39
|
+
@parser ||= OptionParser.new do |opts|
|
40
|
+
opts.banner = <<-EOF
|
41
|
+
Usage:
|
42
|
+
dhun start
|
43
|
+
dhun play spirit
|
44
|
+
dhun pause
|
45
|
+
dhun resume
|
46
|
+
dhun enqueue rahman
|
47
|
+
dhun status
|
48
|
+
dhun stop
|
49
|
+
EOF
|
50
|
+
opts.separator ""
|
51
|
+
opts.separator "Options:"
|
52
|
+
opts.on_tail("-h", "--help", "Show this message") { puts opts; exit }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def parse!
|
57
|
+
parser.parse! @argv
|
58
|
+
@command = @argv.shift
|
59
|
+
@arguments = @argv
|
60
|
+
end
|
61
|
+
|
62
|
+
# Parse the current shell arguments and run the command.
|
63
|
+
# Exits on error.
|
64
|
+
def run!
|
65
|
+
if self.class.commands.include?(@command)
|
66
|
+
if CLIENT_COMMANDS.include?(@command)
|
67
|
+
unless DhunClient.is_dhun_server_running?(@options[:socket])
|
68
|
+
puts "Please start Dhun server first with : dhun start"
|
69
|
+
exit 1
|
70
|
+
end
|
71
|
+
end
|
72
|
+
run_command
|
73
|
+
elsif @command.nil?
|
74
|
+
puts "Command required"
|
75
|
+
puts @parser
|
76
|
+
exit 1
|
77
|
+
else
|
78
|
+
abort "Unknown command: #{@command}. Use one of #{self.class.commands.join(', ')}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def run_command
|
83
|
+
controller = Controller.new(@options)
|
84
|
+
controller.send(@command,*@arguments)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/dhun/server.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
module Dhun
|
3
|
+
class Server
|
4
|
+
|
5
|
+
def initialize(options)
|
6
|
+
@options = options
|
7
|
+
@socket = options[:socket]
|
8
|
+
setup_signals
|
9
|
+
end
|
10
|
+
|
11
|
+
def start
|
12
|
+
puts "Starting Dhun"
|
13
|
+
at_exit { remove_socket_file }
|
14
|
+
EventMachine::run {
|
15
|
+
EventMachine::start_server @socket, DhunServer
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.stop
|
20
|
+
puts "Stopping Dhun"
|
21
|
+
EventMachine.stop if EventMachine.reactor_running?
|
22
|
+
exit
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
protected
|
27
|
+
# Register signals:
|
28
|
+
# * calls +stop+ to shutdown gracefully.
|
29
|
+
def setup_signals
|
30
|
+
trap('QUIT') { Server.stop }
|
31
|
+
trap('INT') { Server.stop }
|
32
|
+
trap('TERM') { Server.stop }
|
33
|
+
end
|
34
|
+
|
35
|
+
def remove_socket_file
|
36
|
+
File.delete(@socket) if File.exist?(@socket)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dhun
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Deepak Jois
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-12-08 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: deepak.jois@gmail.com
|
18
|
+
executables:
|
19
|
+
- dhun
|
20
|
+
extensions:
|
21
|
+
- ext/extconf.rb
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- LICENSE.txt
|
26
|
+
- README.md
|
27
|
+
- Rakefile
|
28
|
+
- TODO.md
|
29
|
+
- bin/dhun
|
30
|
+
- ext/Makefile
|
31
|
+
- ext/dhun.h
|
32
|
+
- ext/dhun_ext.c
|
33
|
+
- ext/extconf.rb
|
34
|
+
- ext/player.c
|
35
|
+
- ext/query.c
|
36
|
+
- lib/dhun.rb
|
37
|
+
- lib/dhun/command.rb
|
38
|
+
- lib/dhun/controller.rb
|
39
|
+
- lib/dhun/dhun_client.rb
|
40
|
+
- lib/dhun/dhun_server.rb
|
41
|
+
- lib/dhun/handler.rb
|
42
|
+
- lib/dhun/player.rb
|
43
|
+
- lib/dhun/query.rb
|
44
|
+
- lib/dhun/result.rb
|
45
|
+
- lib/dhun/runner.rb
|
46
|
+
- lib/dhun/server.rb
|
47
|
+
has_rdoc: true
|
48
|
+
homepage: http://github.com/deepakjois/dhun
|
49
|
+
licenses: []
|
50
|
+
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project: dhun
|
71
|
+
rubygems_version: 1.3.5
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: Minimalist MP3 Player for OS X
|
75
|
+
test_files: []
|
76
|
+
|