dhun 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|