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.
Files changed (45) hide show
  1. data/.document +5 -0
  2. data/Gemfile +26 -0
  3. data/Gemfile.lock +63 -0
  4. data/LICENSE.txt +20 -0
  5. data/README.rdoc +19 -0
  6. data/Rakefile +45 -0
  7. data/VERSION +1 -0
  8. data/app.tmproj +93 -0
  9. data/config/app.yml +5 -0
  10. data/lib/core-extensions/array_ext.rb +5 -0
  11. data/lib/core-extensions/benchmark-logger.rb +31 -0
  12. data/lib/core-extensions/cgi_ext.rb +9 -0
  13. data/lib/core-extensions/hash_ext.rb +24 -0
  14. data/lib/core-extensions/object_ext.rb +8 -0
  15. data/lib/core-extensions/string_ext.rb +59 -0
  16. data/lib/core-extensions/string_without_accents.rb +92 -0
  17. data/lib/core-extensions/tls.rb +16 -0
  18. data/lib/core-extensions/uids.rb +16 -0
  19. data/lib/core-extensions/uri_ext.rb +10 -0
  20. data/lib/extensions/bitly.rb +34 -0
  21. data/lib/extensions/http.rb +60 -0
  22. data/lib/radiospieler.rb +13 -0
  23. data/lib/radiospieler/radiospieler.rb +13 -0
  24. data/lib/radiospieler/radiospieler/cache.rb +77 -0
  25. data/lib/radiospieler/radiospieler/config.rb +58 -0
  26. data/lib/radiospieler/radiospieler/logger.rb +26 -0
  27. data/lib/radiospieler/radiospieler/root.rb +36 -0
  28. data/script/watchr +28 -0
  29. data/test/app_test.rb +56 -0
  30. data/test/array_ext_test.rb +37 -0
  31. data/test/bitlify_test.rb +28 -0
  32. data/test/cache_test.rb +15 -0
  33. data/test/cgi_ext_test.rb +19 -0
  34. data/test/config.yml +8 -0
  35. data/test/config_test.rb +16 -0
  36. data/test/fixtures-vcr/bitly_limit_exceeded.yml +43 -0
  37. data/test/fixtures-vcr/bitly_ok.yml +46 -0
  38. data/test/fixtures-vcr/http_test.yml +389 -0
  39. data/test/hash_ext_test.rb +15 -0
  40. data/test/helper.rb +29 -0
  41. data/test/http_test.rb +18 -0
  42. data/test/string_ext_test.rb +26 -0
  43. data/test/tls_test.rb +35 -0
  44. data/test/uid_test.rb +17 -0
  45. 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,16 @@
1
+ require "digest/md5"
2
+
3
+ class String
4
+ def crc32
5
+ Zlib.crc32(self)
6
+ end
7
+
8
+ def md5
9
+ Digest::MD5.hexdigest(self)
10
+ end
11
+
12
+ # return a 64 bit uid
13
+ def uid64
14
+ md5.unpack("LL").inject { |a,b| (a << 31) + b }
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ require "uri"
2
+
3
+ module URI
4
+ def self.valid?(uri)
5
+ parse(uri)
6
+ rescue URI::InvalidURIError
7
+ false
8
+ end
9
+ end
10
+
@@ -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
@@ -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,13 @@
1
+ module App
2
+ extend self
3
+
4
+ attr :env, true
5
+
6
+ def env
7
+ @env ||= ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
8
+ end
9
+ end
10
+
11
+ Dir.glob("#{File.dirname(__FILE__)}/radiospieler/*.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