radiospieler 0.3.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/.document +5 -0
- data/Gemfile +26 -0
- data/Gemfile.lock +63 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/app.tmproj +93 -0
- data/config/app.yml +5 -0
- data/lib/core-extensions/array_ext.rb +5 -0
- data/lib/core-extensions/benchmark-logger.rb +31 -0
- data/lib/core-extensions/cgi_ext.rb +9 -0
- data/lib/core-extensions/hash_ext.rb +24 -0
- data/lib/core-extensions/object_ext.rb +8 -0
- data/lib/core-extensions/string_ext.rb +59 -0
- data/lib/core-extensions/string_without_accents.rb +92 -0
- data/lib/core-extensions/tls.rb +16 -0
- data/lib/core-extensions/uids.rb +16 -0
- data/lib/core-extensions/uri_ext.rb +10 -0
- data/lib/extensions/bitly.rb +34 -0
- data/lib/extensions/http.rb +60 -0
- data/lib/radiospieler.rb +13 -0
- data/lib/radiospieler/radiospieler.rb +13 -0
- data/lib/radiospieler/radiospieler/cache.rb +77 -0
- data/lib/radiospieler/radiospieler/config.rb +58 -0
- data/lib/radiospieler/radiospieler/logger.rb +26 -0
- data/lib/radiospieler/radiospieler/root.rb +36 -0
- data/script/watchr +28 -0
- data/test/app_test.rb +56 -0
- data/test/array_ext_test.rb +37 -0
- data/test/bitlify_test.rb +28 -0
- data/test/cache_test.rb +15 -0
- data/test/cgi_ext_test.rb +19 -0
- data/test/config.yml +8 -0
- data/test/config_test.rb +16 -0
- data/test/fixtures-vcr/bitly_limit_exceeded.yml +43 -0
- data/test/fixtures-vcr/bitly_ok.yml +46 -0
- data/test/fixtures-vcr/http_test.yml +389 -0
- data/test/hash_ext_test.rb +15 -0
- data/test/helper.rb +29 -0
- data/test/http_test.rb +18 -0
- data/test/string_ext_test.rb +26 -0
- data/test/tls_test.rb +35 -0
- data/test/uid_test.rb +17 -0
- metadata +226 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
class Module
|
2
|
+
def thread_local_attribute(name, &block)
|
3
|
+
proc = Proc.new if block_given?
|
4
|
+
key = "tls:#{name}"
|
5
|
+
|
6
|
+
singleton_class.class_eval do
|
7
|
+
define_method(name) do
|
8
|
+
Thread.current[key] ||= proc ? proc.call : nil
|
9
|
+
end
|
10
|
+
|
11
|
+
define_method("#{name}=") do |value|
|
12
|
+
Thread.current[key] = value
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "cgi"
|
2
|
+
|
3
|
+
module Bitly
|
4
|
+
TIME_TO_LIVE = 365 * 24 * 3600
|
5
|
+
|
6
|
+
def self.url_base
|
7
|
+
return @url_base unless @url_base.nil?
|
8
|
+
|
9
|
+
user, key = (App.config[:bitly] || {}).values_at "user", "key"
|
10
|
+
|
11
|
+
@url_base = unless user && key
|
12
|
+
App.logger.warn "Missing or invalid bitly configuration: #{App.config[:bitly]}"
|
13
|
+
false
|
14
|
+
else
|
15
|
+
"https://api-ssl.bitly.com/v3/shorten?login=#{user}&apiKey=#{key}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.shorten(url)
|
20
|
+
return url if !self.url_base || !url || url.index("/bit.ly/")
|
21
|
+
|
22
|
+
bitly_url = "#{self.url_base}&longUrl=#{CGI.escape(url)}"
|
23
|
+
parsed = JSON.parse Http.get(bitly_url, TIME_TO_LIVE)
|
24
|
+
|
25
|
+
if parsed["status_code"] == 200
|
26
|
+
bitlified = parsed["data"]["url"]
|
27
|
+
App.logger.debug "bitlify: #{url} => #{bitlified}"
|
28
|
+
bitlified
|
29
|
+
else
|
30
|
+
App.logger.error "#{url}: bitly error: #{parsed["status_txt"] }"
|
31
|
+
url
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
# The Http module defines a
|
4
|
+
#
|
5
|
+
# Http.get(url)
|
6
|
+
#
|
7
|
+
# method.
|
8
|
+
module Http
|
9
|
+
extend self
|
10
|
+
|
11
|
+
# the default expiration time for get requests.
|
12
|
+
module MaxAge
|
13
|
+
def self.config
|
14
|
+
@config ||= (App.config["cache-max-age"] || {}).to_a
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.for(url)
|
18
|
+
config.each do |pattern, max_age|
|
19
|
+
return max_age if url.index(pattern)
|
20
|
+
end
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def get(url, max_age = MaxAge.for(url))
|
26
|
+
App.logger.benchmark("[GET] #{url}", :minimum => 20) do
|
27
|
+
App.cached(url, max_age) do
|
28
|
+
App.logger.debug "[GET] #{url}"
|
29
|
+
get_(url)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def get_(uri_str, limit = 10)
|
37
|
+
raise 'too many redirections' if limit == 0
|
38
|
+
|
39
|
+
uri = URI.parse(uri_str)
|
40
|
+
|
41
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
42
|
+
if uri.scheme == "https"
|
43
|
+
http.use_ssl = true
|
44
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
45
|
+
end
|
46
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
47
|
+
response = http.request(request)
|
48
|
+
|
49
|
+
case response
|
50
|
+
when Net::HTTPSuccess then
|
51
|
+
response.body
|
52
|
+
when Net::HTTPRedirection then
|
53
|
+
location = response['location']
|
54
|
+
App.logger.debug "redirected to #{location}"
|
55
|
+
get_(location, limit - 1)
|
56
|
+
else
|
57
|
+
response.value
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/radiospieler.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# require "micro_sql"
|
2
|
+
|
3
|
+
Dir.glob("#{File.dirname(__FILE__)}/core-extensions/*.rb").sort.each do |file|
|
4
|
+
load file
|
5
|
+
end
|
6
|
+
|
7
|
+
Dir.glob("#{File.dirname(__FILE__)}/radiospieler/*.rb").sort.each do |file|
|
8
|
+
load file
|
9
|
+
end
|
10
|
+
|
11
|
+
Dir.glob("#{File.dirname(__FILE__)}/extensions/*.rb").sort.each do |file|
|
12
|
+
load file
|
13
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require_relative "config"
|
2
|
+
|
3
|
+
#
|
4
|
+
# This is a cache module, which keeps entries for a certain time period,
|
5
|
+
# stored away in a redis store.
|
6
|
+
#
|
7
|
+
# Entries are packed via Marshal.
|
8
|
+
module App
|
9
|
+
module Cache
|
10
|
+
DEFAULT_MAX_AGE = 4 * 3600 # 4 hours.
|
11
|
+
|
12
|
+
def self.clear
|
13
|
+
store.flushdb
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.uid(key)
|
17
|
+
case key
|
18
|
+
when String, Hash then key.uid64
|
19
|
+
when Fixnum then key
|
20
|
+
else
|
21
|
+
App.logger.warn "Don't know how to deal with non-uid key #{key}"
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.cached(key, max_age = DEFAULT_MAX_AGE, &block)
|
27
|
+
cache_id = uid(key)
|
28
|
+
|
29
|
+
return yield if !store || !max_age || !cache_id
|
30
|
+
|
31
|
+
if marshalled = store.get(cache_id)
|
32
|
+
unmarshal(marshalled)
|
33
|
+
else
|
34
|
+
yield.tap { |v|
|
35
|
+
store.set(cache_id, marshal(v))
|
36
|
+
store.expire(cache_id, max_age)
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.unmarshal(marshalled)
|
42
|
+
Marshal.load Base64.decode64(marshalled) if marshalled
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.marshal(value)
|
46
|
+
Base64.encode64 Marshal.dump(value)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.store_from_url(url)
|
50
|
+
return unless url
|
51
|
+
uri = URI.parse(url)
|
52
|
+
case uri.scheme
|
53
|
+
when "redis"
|
54
|
+
require "redis"
|
55
|
+
Redis.connect(:url => url)
|
56
|
+
when nil
|
57
|
+
SimpleCache.new(uri.path)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
attr :store, true
|
62
|
+
extend self
|
63
|
+
|
64
|
+
def self.store
|
65
|
+
@store ||= store_from_url(App.config[:cache]) || begin
|
66
|
+
name = "#{File.basename(App.root)}-#{App.root.uid64}"
|
67
|
+
store = SimpleCache.new(name)
|
68
|
+
App.logger.warn "No or invalid :cache configuration, fallback to #{store.path}"
|
69
|
+
store
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def cached(key, max_age = Cache::DEFAULT_MAX_AGE, &block)
|
75
|
+
Cache.cached(key, max_age, &block)
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative "root"
|
2
|
+
require_relative "logger"
|
3
|
+
|
4
|
+
require "yaml"
|
5
|
+
require "erb"
|
6
|
+
|
7
|
+
module App
|
8
|
+
module Config
|
9
|
+
def self.paths
|
10
|
+
[ "#{App.root}/config/app.yml", "#{App.root}/config.yml" ]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.read(path)
|
14
|
+
return unless File.exist?(path)
|
15
|
+
App.logger.info "Reading configuration from #{path}"
|
16
|
+
erb = File.read(path)
|
17
|
+
yaml = ERB.new(erb).result(binding)
|
18
|
+
YAML.load(yaml) || {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.load
|
22
|
+
config = paths.inject(nil) do |c, path|
|
23
|
+
c || read(path)
|
24
|
+
end
|
25
|
+
|
26
|
+
config ||= begin
|
27
|
+
App.logger.warn "No configuration found in #{App.root}"
|
28
|
+
{}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
@@configurations = {}
|
33
|
+
|
34
|
+
def self.current
|
35
|
+
@@configurations[App.root] ||= begin
|
36
|
+
config = self.load
|
37
|
+
default_settings = config["default"] || {}
|
38
|
+
current_settings = config[App.env] || {}
|
39
|
+
default_settings.update current_settings
|
40
|
+
end.extend(TreatSymbolsAsStrings)
|
41
|
+
end
|
42
|
+
|
43
|
+
module TreatSymbolsAsStrings
|
44
|
+
def [](key)
|
45
|
+
fetch(key) do
|
46
|
+
case key
|
47
|
+
when Symbol then fetch(key.to_s, nil)
|
48
|
+
when String then fetch(key.to_sym, nil)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def config
|
56
|
+
Config.current
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "logger"
|
2
|
+
|
3
|
+
module App
|
4
|
+
def self.logger
|
5
|
+
@logger
|
6
|
+
end
|
7
|
+
|
8
|
+
INTENDATIONS = {
|
9
|
+
"DEBUG" => ' ',
|
10
|
+
"INFO" => ' **',
|
11
|
+
"WARN" => ' ****',
|
12
|
+
"ERROR" => ' ******',
|
13
|
+
"FATAL" => '********'
|
14
|
+
}
|
15
|
+
|
16
|
+
def self.logger=(logger)
|
17
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
18
|
+
intend = INTENDATIONS[severity] || INTENDATIONS["FATAL"]
|
19
|
+
"#{intend} #{msg}\n"
|
20
|
+
end
|
21
|
+
|
22
|
+
@logger = logger
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
App.logger = Logger.new(STDERR)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
module App
|
3
|
+
# -- app root ---------------------------------------------------------------
|
4
|
+
|
5
|
+
module Root
|
6
|
+
def self.find_starting_in(dir)
|
7
|
+
if is_root?(dir)
|
8
|
+
dir
|
9
|
+
elsif !dir.sub!(/\/[^\/]+$/, "")
|
10
|
+
nil
|
11
|
+
else
|
12
|
+
find_starting_in(dir)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.is_root?(dir)
|
17
|
+
File.exists?("#{dir}/config.ru") ||
|
18
|
+
File.exists?("#{dir}/Gemfile") ||
|
19
|
+
File.exists?("#{dir}/Procfile")
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.find
|
23
|
+
find_starting_in(Dir.getwd) || raise("Could not find application root for #{dir}")
|
24
|
+
end
|
25
|
+
|
26
|
+
def root=(root)
|
27
|
+
@root = root
|
28
|
+
end
|
29
|
+
|
30
|
+
def root
|
31
|
+
@root ||= Root.find.tap { |root| App.logger.warn "Application root is #{root}" }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
extend Root
|
36
|
+
end
|
data/script/watchr
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rb-fsevent'
|
3
|
+
|
4
|
+
if ARGV.length < 2
|
5
|
+
STDERR.puts "watchr dir ruby-parameter(s)..."
|
6
|
+
abort
|
7
|
+
end
|
8
|
+
|
9
|
+
paths = ARGV.shift.split(",")
|
10
|
+
$args = ARGV.dup
|
11
|
+
|
12
|
+
|
13
|
+
def do_ruby
|
14
|
+
STDERR.puts $args.join(" ")
|
15
|
+
STDERR.puts "=" * 80
|
16
|
+
|
17
|
+
system(*$args)
|
18
|
+
end
|
19
|
+
|
20
|
+
puts "Initial run"
|
21
|
+
do_ruby
|
22
|
+
|
23
|
+
fsevent = FSEvent.new
|
24
|
+
fsevent.watch paths do |directories|
|
25
|
+
puts "Detected change inside: #{directories.inspect}"
|
26
|
+
do_ruby
|
27
|
+
end
|
28
|
+
fsevent.run
|
data/test/app_test.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestApp < Test::Unit::TestCase
|
4
|
+
def test_root
|
5
|
+
expected = File.expand_path("#{File.dirname(__FILE__)}/..")
|
6
|
+
|
7
|
+
assert_equal expected, App::Root.find_starting_in(File.dirname(__FILE__))
|
8
|
+
assert_equal expected, App::Root.find
|
9
|
+
assert_equal expected, App.root
|
10
|
+
|
11
|
+
App.root = "foo"
|
12
|
+
assert_equal "foo", App.root
|
13
|
+
|
14
|
+
App.root = nil
|
15
|
+
assert_equal expected, App.root
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_object_attributes
|
19
|
+
a = {}
|
20
|
+
assert_raise(NoMethodError) {
|
21
|
+
a.foo
|
22
|
+
}
|
23
|
+
|
24
|
+
a.define_attribute :foo, "bar"
|
25
|
+
assert_equal("bar", a.foo)
|
26
|
+
|
27
|
+
a.define_attribute :foo, "baz"
|
28
|
+
assert_equal("baz", a.foo)
|
29
|
+
|
30
|
+
a.define_attribute :foo, nil
|
31
|
+
assert_equal(nil, a.foo)
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_benchmark_logger
|
35
|
+
r = []
|
36
|
+
|
37
|
+
logger = Logger.new(STDERR)
|
38
|
+
logger.formatter = proc { |_, _, _, msg| r << msg; nil }
|
39
|
+
|
40
|
+
foo = logger.benchmark {
|
41
|
+
"foo"
|
42
|
+
}
|
43
|
+
assert_equal("foo", foo)
|
44
|
+
assert_equal(["Benchmark: 0 msecs"], r)
|
45
|
+
|
46
|
+
r = []
|
47
|
+
foo = logger.benchmark("message") { "foo" }
|
48
|
+
assert_equal("foo", foo)
|
49
|
+
assert_equal(["message: 0 msecs"], r)
|
50
|
+
|
51
|
+
r = []
|
52
|
+
foo = logger.benchmark("message", :minimum => 1) { "foo" }
|
53
|
+
assert_equal("foo", foo)
|
54
|
+
assert_equal([], r)
|
55
|
+
end
|
56
|
+
end
|