radiospieler 0.3.0

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