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