13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 | Rails.backtrace_cleaner.add_silencer { |line| line =~ /rvm/ }
9 |
--------------------------------------------------------------------------------
/app/views/layouts/_servicepanel.html.erb:
--------------------------------------------------------------------------------
1 |
You may have mistyped the address or the page may have moved.
37 |
38 |
39 | Let us take you to the main page and we can start all over again, shall we?
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/example.server_config.yml:
--------------------------------------------------------------------------------
1 | # The settings in this file will change how the Rails app behaves. Most users shouldn't have to change these settings.
2 |
3 |
4 | # This should be set to whatever IP address your host will respond to requests from.
5 | # Most *nix users can leave this at 0.0.0.0, but if you have issues then you'll need to bind to a specific interface.
6 | # run: ifconfig to find out what IP addresses your system has configured.
7 | # Also 0.0.0.0 may be invalid on some systems like Windows and some Raspberry Pi distros (or when IPv6 is enabled sometimes)
8 | # so if you have issues then set it to something else
9 | # Example: 192.168.0.10
10 | # Default: 0.0.0.0
11 | web_host: 0.0.0.0
12 |
13 |
14 | # This is the port that Rails will bind to. If you want to run the app on default http port 80 then you'll probably need
15 | # to run it as root.
16 | # Example: 80
17 | # Default: 3000
18 | port: 3000
19 |
20 |
21 | # Most users won't run the app behind a reverse proxy, if you don't know that that is then leave this alone.
22 | # Example: true
23 | # Default: false
24 | use_reverse_proxy: false
25 |
26 |
27 | # relative_root should be set to the path that the app is under. So "/porthole" (omit the trailing "/") would
28 | # mount the app under (using 127.0.0.1 as an example web_host) http://127.0.0.1/porthole
29 |
30 | # !!!!!!!!!!!! If you change relative_root you will need to regenerate your assets by running serverSetup.sh !!!!!!!!!!!!
31 |
32 | # Example: /porthole
33 | # Default:
34 | relative_root:
--------------------------------------------------------------------------------
/test/fixtures/plex_objects.yml:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: plex_objects
4 | #
5 | # id :integer not null, primary key
6 | # image :string
7 | # thumb_url :string not null
8 | # description :text
9 | # created_at :datetime not null
10 | # updated_at :datetime not null
11 | # plex_object_flavor_id :integer
12 | # plex_object_flavor_type :string
13 | # media_title :string not null
14 | #
15 |
16 | plex_object_session_1:
17 | image: placeholder.png
18 | media_title: Movie1
19 | description: .
20 | thumb_url: /library/metadata/38501/thumb/1444964730
21 | plex_object_flavor: plex_service_w1sess_session_1 (PlexSession)
22 |
23 | plex_object_session_2:
24 | image: placeholder.png
25 | media_title: Movie1
26 | description: .
27 | thumb_url: /library/metadata/38501/thumb/1444964730
28 | plex_object_flavor: plex_service_w2sess_session_1 (PlexSession)
29 |
30 | plex_object_session_3:
31 | image: placeholder.png
32 | media_title: Movie1
33 | description: .
34 | thumb_url: /library/metadata/38501/thumb/1444964730
35 | plex_object_flavor: plex_service_w2sess_session_2 (PlexSession)
36 |
37 | plex_object_recently_added_1:
38 | image: placeholder.png
39 | media_title: Movie1
40 | description: .
41 | thumb_url: /library/metadata/38501/thumb/1444964730
42 | plex_object_flavor: plex_service_w1ra_pra_1 (PlexRecentlyAdded)
43 |
--------------------------------------------------------------------------------
/example.windows.server_config.yml:
--------------------------------------------------------------------------------
1 | # The settings in this file will change how the Rails app behaves. Most users shouldn't have to change these settings.
2 |
3 |
4 | # This should be set to whatever IP address your host will respond to requests from.
5 | # Most *nix users can leave this at 0.0.0.0, but if you have issues then you'll need to bind to a specific interface.
6 | # run: ifconfig to find out what IP addresses your system has configured.
7 | # Also 0.0.0.0 may be invalid on some systems like Windows and some Raspberry Pi distros (or when IPv6 is enabled sometimes)
8 | # so if you have issues then set it to something else
9 | # Example: 192.168.0.10
10 | # Default: 127.0.0.1
11 | web_host: 127.0.0.1
12 |
13 |
14 | # This is the port that Rails will bind to. If you want to run the app on default http port 80 then you'll probably need
15 | # to run it as root.
16 | # Example: 80
17 | # Default: 3000
18 | port: 3000
19 |
20 |
21 | # Most users won't run the app behind a reverse proxy, if you don't know that that is then leave this alone.
22 | # Example: true
23 | # Default: false
24 | use_reverse_proxy: false
25 |
26 |
27 | # relative_root should be set to the path that the app is under. So "/porthole" (omit the trailing "/") would
28 | # mount the app under (using 127.0.0.1 as an example web_host) http://127.0.0.1/porthole
29 |
30 | # !!!!!!!!!!!! If you change relative_root you will need to regenerate your assets by running serverSetup.sh !!!!!!!!!!!!
31 |
32 | # Example: /porthole
33 | # Default:
34 | relative_root:
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | gem 'rails', '5.1.4'
3 | gem 'rake', '~> 12.1.0'
4 | gem 'sqlite3', '~> 1.3.13'
5 | gem 'sass-rails', '~> 5.0.6'
6 | gem 'uglifier', '~> 3.1.9'
7 | gem 'coffee-rails', '~> 4.2.1'
8 | gem 'jquery-rails', '~> 4.3.1'
9 | gem 'turbolinks', '~> 5.0.0'
10 | gem 'autoprefixer-rails', '~> 6.7.7.1'
11 | gem 'jbuilder', '~> 2.6.3'
12 | gem 'bootstrap-sass', '~> 3.3.7'
13 | gem 'sprockets-rails', '~> 3.2.0'
14 | gem 'simple_form', '~> 3.5.0'
15 | gem 'strip_attributes', '~> 1.8.0'
16 | gem 'rest-client', '~> 2.0.2'
17 | gem 'json', '~> 2.1.0'
18 | gem 'sass', '~> 3.4.23'
19 | gem 'execjs', '~> 2.7.0'
20 | gem 'parallel', '~> 1.11.1'
21 | gem 'forecast_io', '~> 2.0'
22 | gem 'geocoder', '~> 1.4.3'
23 | gem 'tzinfo', '~> 1.2.3'
24 | gem 'lograge', '~> 0.6.0'
25 | # gem 'browserlog', git: 'https://github.com/scytherswings/browserlog'
26 |
27 | group :development, :test do
28 | gem 'spring', '~> 2.0.1'
29 | gem 'parallel_tests', '~> 2.14.0'
30 | end
31 |
32 | group :development do
33 | gem 'annotate', require: false
34 | # gem 'web-console', '~> 2.0'
35 | end
36 |
37 | group :test do
38 | gem 'rails-controller-testing', '~> 1.0.1'
39 | gem 'simplecov', '~> 0.14.1'
40 | gem 'coveralls', '~> 0.8.21'
41 | gem 'minitest-reporters', '~> 1.1.14'
42 | gem 'webmock', '~> 2.3.2'
43 | gem 'vcr', '~> 3.0.3'
44 | gem 'minitest-vcr', '~> 1.4.0'
45 | gem 'faker', '~> 1.8.4'
46 | gem 'fabrication', '~> 2.16.1'
47 | end
48 |
49 | group :production do
50 | gem 'puma', '~> 3.10'
51 | end
52 |
53 | gem 'tzinfo-data'
--------------------------------------------------------------------------------
/app/views/layouts/_navbar.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/views/services/_service.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <% if service.online_status %>
8 | <%= link_to service.url, class: "h2_index_link", :target => "_blank" do %>
9 |
11 | <% end %>
12 | <% else %>
13 | <%= link_to service.url, class: "h2_index_link", :target => "_blank" do %>
14 |
16 | <% end %>
17 | <% end %>
18 |
19 |
Porthole is an open-source project meant to provide a simple-to-use dashboard for administrators and users of Plex servers.
8 | Monitors of services is done by opening a TCP socket to the port you specify when configuring them.
58 |
59 | <% end %>
--------------------------------------------------------------------------------
/app/helpers/api_helper.rb:
--------------------------------------------------------------------------------
1 | module ApiHelper
2 | def api_request(method:, url:, headers:, payload: nil, user: nil, verify_ssl: true)
3 | if url.nil? || url.blank?
4 | raise ArgumentError, 'api_request was called with a nil/blank url'
5 | end
6 | if user.nil?
7 | api_call(method, url, headers, payload, verify_ssl)
8 | else
9 | basic_auth(user, method, url, headers, payload, verify_ssl)
10 | end
11 | end
12 |
13 | def api_call(method, url, headers, payload, verify_ssl)
14 | response = RestClient::Request.execute method: method,
15 | url: url,
16 | headers: headers,
17 | payload: payload,
18 | verify_ssl: verify_ssl,
19 | timeout: 1
20 | JSON.parse(response)
21 | rescue JSON::ParserError
22 | logger.error 'There was an error parsing an API response. See debug logs for details.'
23 | # log_request_data(request: request, response: response)
24 | logger.debug "The error was caused by: #{response.to_s}"
25 | nil
26 | end
27 |
28 | def basic_auth(user, method, url, headers, payload, verify_ssl)
29 | response = RestClient::Request.execute method: method,
30 | url: url,
31 | user: user.username,
32 | password: user.password,
33 | headers: headers,
34 | payload: payload,
35 | verify_ssl: verify_ssl,
36 | timeout: 1
37 | JSON.parse(response)
38 | rescue JSON::ParserError
39 | logger.error 'There was an error parsing an API response. See debug logs for details.'
40 | # log_request_data(request: request, response: response)
41 | logger.debug "The error was caused by: #{response.to_s}"
42 | nil
43 | end
44 |
45 | # def log_request_data(request:, response:, log_level: Logger::DEBUG)
46 | # logger.add(log_level){'Request and response data below...'}
47 | # logger.add(log_level){"Request method: #{request.method.upcase}"}
48 | #
49 | # #logging the payload isn't possible currently because of
50 | # # https://github.com/rest-client/rest-client/issues/357
51 | # unless request.payload.nil?
52 | # # logger.add(log_level){"Request Payload: #{request.payload}"}
53 | # logger.add(log_level){"Request Payload: #{request.args[:payload].inspect}"}
54 | # end
55 | #
56 | # logger.add(log_level){"Headers: #{request.headers}"}
57 | #
58 | # unless request.cookies.empty?
59 | # logger.add(log_level){"Cookies: #{request.cookies}"}
60 | # end
61 | # logger.add(log_level){"URL: #{request.url}"}
62 | #
63 | # if log_level == Logger::DEBUG || log_level > Logger::WARN
64 | # logger.add(log_level){"Response: #{response}"}
65 | # end
66 | #
67 | # unless response.cookies.empty?
68 | # logger.add(log_level){"Response cookies: #{response.cookies}"}
69 | # end
70 | # end
71 | end
--------------------------------------------------------------------------------
/test/models/plex_session_test.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: plex_sessions
4 | #
5 | # id :integer not null, primary key
6 | # progress :integer not null
7 | # total_duration :integer not null
8 | # plex_user_name :string
9 | # created_at :datetime not null
10 | # updated_at :datetime not null
11 | # session_key :string not null
12 | # plex_service_id :integer
13 | # stream_type :string not null
14 | #
15 |
16 | require 'test_helper'
17 |
18 | class PlexSessionTest < ActiveSupport::TestCase
19 |
20 | test 'sessions should be valid' do
21 | assert @plex_service_w1sess_session_1.valid?, 'Session_one was invalid'
22 | assert @plex_service_w2sess_session_1.valid?, 'Session_two was invalid'
23 | assert @plex_service_w2sess_session_2.valid?, 'Session_three was invalid'
24 | end
25 |
26 | test 'plex_session should have progress' do
27 | plex_session = @plex_service_w1sess_session_1
28 | plex_session.progress = nil
29 | assert_not plex_session.valid?
30 | end
31 |
32 | test 'plex_session should have total_duration' do
33 | plex_session = @plex_service_w1sess_session_1
34 | plex_session.total_duration = nil
35 | assert_not plex_session.valid?
36 | end
37 |
38 | test 'plex_user_name should be present' do
39 | @plex_service_w1sess_session_1.plex_user_name = nil
40 | assert_not @plex_service_w1sess_session_1.valid?, 'plex_user_name should not be nil'
41 | @plex_service_w1sess_session_1.plex_user_name = ''
42 | assert_not @plex_service_w1sess_session_1.valid?, 'plex_user_name should not be empty string'
43 | end
44 |
45 | test 'user_name should not be allowed to be whitespace only' do
46 | @plex_service_w1sess_session_1.plex_user_name = ' '
47 | assert_not @plex_service_w1sess_session_1.valid?, 'plex_user_name should not be valid with whitespace string'
48 | end
49 |
50 | test 'session should be unique' do
51 | duplicate_session = @plex_service_w1sess_session_1.dup
52 | @plex_service_w1sess_session_1.save
53 | assert_not duplicate_session.valid?, 'Duplicate session should not be valid'
54 | end
55 |
56 | test 'session will correctly identify stream_type Stream' do
57 | assert_equal('Stream', PlexSession.determine_stream_type('copy'),
58 | 'copy videoDecision was not correctly translated to Stream')
59 | assert_equal('Stream', PlexSession.determine_stream_type('COPY'),
60 | 'COPY videoDecision was not correctly translated to Stream')
61 | end
62 |
63 | test 'session will correctly identify stream_type Transcode' do
64 | assert_equal('Transcode', PlexSession.determine_stream_type('transcode'),
65 | 'transcode videoDecision was not correctly translated to Transcode')
66 | assert_equal('Transcode', PlexSession.determine_stream_type('TRANSCODE'),
67 | 'TRANSCODE videoDecision was not correctly translated to Transcode')
68 | end
69 |
70 | test 'session will correctly default stream_type nil or empty to Stream' do
71 | assert_equal('Stream', PlexSession.determine_stream_type(''),
72 | 'empty string videoDecision was not correctly translated to Stream')
73 | assert_equal('Stream', PlexSession.determine_stream_type(nil),
74 | 'nil videoDecision was not correctly translated to Stream')
75 | end
76 |
77 | #Tests for Plex integration
78 |
79 |
80 | end
81 |
--------------------------------------------------------------------------------
/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # Note that this schema.rb definition is the authoritative source for your
6 | # database schema. If you need to create the application database on another
7 | # system, you should be using db:schema:load, not running all the migrations
8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9 | # you'll amass, the slower it'll run and the greater likelihood for issues).
10 | #
11 | # It's strongly recommended that you check this file into your version control system.
12 |
13 | ActiveRecord::Schema.define(version: 20170428012623) do
14 |
15 | create_table "plex_objects", force: :cascade do |t|
16 | t.string "image"
17 | t.string "thumb_url", null: false
18 | t.text "description"
19 | t.datetime "created_at", null: false
20 | t.datetime "updated_at", null: false
21 | t.integer "plex_object_flavor_id"
22 | t.string "plex_object_flavor_type"
23 | t.string "media_title", null: false
24 | t.index ["plex_object_flavor_id"], name: "index_plex_objects_on_plex_object_flavor_id"
25 | t.index ["plex_object_flavor_type"], name: "index_plex_objects_on_plex_object_flavor_type"
26 | end
27 |
28 | create_table "plex_recently_addeds", force: :cascade do |t|
29 | t.datetime "added_date", null: false
30 | t.datetime "created_at", null: false
31 | t.datetime "updated_at", null: false
32 | t.integer "plex_service_id"
33 | t.string "uuid", null: false
34 | t.index ["plex_service_id"], name: "index_plex_recently_addeds_on_plex_service_id"
35 | t.index ["uuid"], name: "index_plex_recently_addeds_on_uuid"
36 | end
37 |
38 | create_table "plex_services", force: :cascade do |t|
39 | t.string "token"
40 | t.datetime "created_at", null: false
41 | t.datetime "updated_at", null: false
42 | end
43 |
44 | create_table "plex_sessions", force: :cascade do |t|
45 | t.integer "progress", null: false
46 | t.integer "total_duration", null: false
47 | t.string "plex_user_name"
48 | t.datetime "created_at", null: false
49 | t.datetime "updated_at", null: false
50 | t.string "session_key", null: false
51 | t.integer "plex_service_id"
52 | t.string "stream_type", null: false
53 | t.index ["plex_service_id"], name: "index_plex_sessions_on_plex_service_id"
54 | t.index ["session_key"], name: "index_plex_sessions_on_session_key"
55 | end
56 |
57 | create_table "server_loads", force: :cascade do |t|
58 | t.string "name"
59 | t.integer "order"
60 | t.datetime "created_at", null: false
61 | t.datetime "updated_at", null: false
62 | end
63 |
64 | create_table "services", force: :cascade do |t|
65 | t.string "name", null: false
66 | t.string "dns_name"
67 | t.string "ip"
68 | t.string "url", null: false
69 | t.integer "port", null: false
70 | t.integer "service_flavor_id"
71 | t.string "service_flavor_type"
72 | t.datetime "created_at", null: false
73 | t.datetime "updated_at", null: false
74 | t.index ["service_flavor_id"], name: "index_services_on_service_flavor_id"
75 | t.index ["service_flavor_type"], name: "index_services_on_service_flavor_type"
76 | end
77 |
78 | create_table "weathers", force: :cascade do |t|
79 | t.string "api_key"
80 | t.float "latitude"
81 | t.float "longitude"
82 | t.datetime "created_at", null: false
83 | t.datetime "updated_at", null: false
84 | t.string "address"
85 | t.text "units"
86 | t.string "city"
87 | t.string "state"
88 | end
89 |
90 | end
91 |
--------------------------------------------------------------------------------
/test/models/plex_object_test.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: plex_objects
4 | #
5 | # id :integer not null, primary key
6 | # image :string
7 | # thumb_url :string not null
8 | # description :text
9 | # created_at :datetime not null
10 | # updated_at :datetime not null
11 | # plex_object_flavor_id :integer
12 | # plex_object_flavor_type :string
13 | # media_title :string not null
14 | #
15 |
16 | require 'test_helper'
17 |
18 | class PlexObjectTest < ActiveSupport::TestCase
19 |
20 | test 'plex_objects should be valid' do
21 | assert @plex_object_session_2.valid?
22 | assert @plex_object_session_1.valid?
23 | assert @plex_object_session_3.valid?
24 | assert @plex_object_recently_added_1.valid?
25 | end
26 |
27 | # test 'plex_object with no plex_session should not be valid' do
28 | # skip('This test needs more thought')
29 | # @plex_object_session_1.plex_object_flavor = nil
30 | # assert_not @plex_object_session_1.valid?, 'Plex object should not be valid if there is no parent plex session'
31 | # end
32 |
33 | test 'plex_object media_title must not be blank' do
34 | @plex_object_session_1.media_title = nil
35 | assert_not @plex_object_session_1.valid?, 'media_title should not be allowed to be nil'
36 | @plex_object_session_1.media_title = ''
37 | assert_not @plex_object_session_1.valid?, 'media_title should not be allowed to be an empty string'
38 | end
39 |
40 | test 'user_name should not be allowed to be whitespace only' do
41 | @plex_object_session_1.media_title = ' '
42 | assert_not @plex_object_session_1.valid?, 'media_title should not be allowed to be a whitespace string'
43 | end
44 |
45 | test 'plex_object should successfully retrieve image' do
46 | # Temporary until I figure out why this directory doesn't get created in proper time.
47 | unless File.directory? Rails.root.join 'test/test_images'
48 | skip 'The test/test_images folder does not exist. This test will probably fail.'
49 | end
50 | assert @plex_object_session_1.delete_thumbnail, 'Deleting thumbnail failed'
51 | assert_not File.file?(Rails.root.join 'test/test_images', (@plex_object_session_1.id.to_s + '.jpeg')),
52 | 'Image file should not be present'
53 | assert_not_nil @plex_object_session_1.get_img, 'Image file was not retrieved'
54 | assert File.file?(Rails.root.join 'test/test_images', (@plex_object_session_1.id.to_s + '.jpeg')),
55 | 'Image file was not found'
56 | end
57 |
58 | test 'destroying a plex_object will delete the associated image' do
59 | # Temporary until I figure out why this directory doesn't get created in proper time.
60 | unless File.directory? Rails.root.join 'test/test_images'
61 | skip 'The test/test_images folder does not exist. This test will probably fail.'
62 | end
63 | assert_not_nil @plex_object_session_1.get_img, 'Image file was not retrieved'
64 | assert File.file?(Rails.root.join 'test/test_images', (@plex_object_session_1.id.to_s + '.jpeg')),
65 | 'Image file was not found'
66 | assert @plex_object_session_1.destroy, 'Destroying the session failed'
67 | assert_not File.file?(Rails.root.join 'test/test_images', (@plex_object_session_1.id.to_s + '.jpeg')),
68 | 'The image file was not deleted'
69 | end
70 |
71 | # test 'destroying a plex_object with a nil image will be successful' do
72 | # ps = Fabricate(:plex_service)
73 | # # ps.plex_recently_added.plex_object.image = nil
74 | #
75 | # # assert(ps.plex_recently_added.destroy, 'The PlexObject was not destroyed')
76 | # end
77 | end
78 |
--------------------------------------------------------------------------------
/test/models/weather_test.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: weathers
4 | #
5 | # id :integer not null, primary key
6 | # api_key :string
7 | # latitude :float
8 | # longitude :float
9 | # created_at :datetime not null
10 | # updated_at :datetime not null
11 | # address :string
12 | # units :text
13 | # city :string
14 | # state :string
15 | #
16 |
17 | require 'test_helper'
18 |
19 | class WeatherTest < ActiveSupport::TestCase
20 | test 'weather with lat and long is valid' do
21 | weather = Fabricate(:weather)
22 | assert weather.valid?, "Weather with lat: #{weather.latitude} and long: #{weather.longitude} was not valid."
23 | end
24 |
25 | test 'a provided address will be converted to lat and long' do
26 | weather = Fabricate.build(:weather)
27 | weather.latitude = nil
28 | weather.longitude = nil
29 | weather.address = '2300 Traverwood Dr, Ann Arbor, MI 48105'
30 | weather.save!
31 | assert_equal 42.306642, weather.latitude
32 | assert_equal -83.71466199999999, weather.longitude
33 | assert weather.valid?, "Weather with address: \"#{weather.address}\" should have been valid"
34 | end
35 |
36 | test 'weather with only latitude is not valid' do
37 | weather = Fabricate.build(:weather)
38 | weather.longitude = nil
39 | weather.latitude = 35.2341
40 | assert_not weather.valid?, 'Weather should not be valid if only supplied latitude.'
41 | end
42 |
43 | test 'weather with only longitude is not valid' do
44 | weather = Fabricate.build(:weather)
45 | weather.longitude = 88.0515
46 | weather.latitude = nil
47 | assert_not weather.valid?, 'Weather should not be valid if only supplied longitude.'
48 | end
49 |
50 | test 'weather with no api key is not valid' do
51 | weather = Fabricate.build(:weather)
52 | weather.api_key = nil
53 | assert_not weather.valid?, 'Weather should not be valid if there is no api key.'
54 | end
55 |
56 | test 'an invalid address will throw a RecordInvalid exception' do
57 | expected_message = 'Fetching the precise location failed. Please check that the address is valid.'
58 | weather = Fabricate.build(:weather)
59 | weather.longitude = nil
60 | weather.latitude = nil
61 | weather.address = 'This is not a valid address'
62 |
63 | exception = assert_raises(ActiveRecord::RecordInvalid) { weather.save! }
64 | assert exception.message.include?(expected_message),
65 | "The exception didn't mention the address error message as expected. \nGot: '#{exception.message}'\nwhen expecting: '#{expected_message}'."
66 | end
67 |
68 | test 'an nil address will throw a RecordInvalid exception' do
69 | expected_message = 'Fetching the precise location failed. Please check that the address is valid.'
70 | weather = Fabricate.build(:weather)
71 | weather.longitude = nil
72 | weather.latitude = nil
73 | weather.address = nil
74 |
75 | exception = assert_raises(ActiveRecord::RecordInvalid) { weather.save! }
76 | assert exception.message.include?(expected_message),
77 | "The exception didn't mention the address error message as expected. \nGot: '#{exception.message}'\nwhen expecting: '#{expected_message}'."
78 | end
79 |
80 | test 'weather can be retrieved with valid parameters' do
81 | weather = Fabricate(:weather)
82 | weather.latitude = 42.306642
83 | weather.longitude = -83.71466199999999
84 | weather.api_key = '0ca1d9fc73742b2dca0dc2643d89994d'
85 | assert_not_nil weather.get_weather, 'weather.get_weather should not return nil for a valid weather object'
86 | assert_equal 42.306642, weather.get_weather['latitude']
87 | assert_equal -83.71466199999999, weather.get_weather['longitude']
88 | end
89 | end
90 |
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both threaded web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Attempt to read encrypted secrets from `config/secrets.yml.enc`.
18 | # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or
19 | # `config/secrets.yml.key`.
20 | config.read_encrypted_secrets = false
21 |
22 | # Disable serving static files from the `/public` folder by default since
23 | # Apache or NGINX already handles this.
24 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'] || true
25 |
26 | # Compress JavaScripts and CSS.
27 | config.assets.js_compressor = :uglifier
28 | # config.assets.css_compressor = :sass
29 |
30 | # Do not fallback to assets pipeline if a precompiled asset is missed.
31 | config.assets.compile = ENV['RAILS_SERVE_STATIC_FILES'] || true
32 |
33 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
34 |
35 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
36 | # config.action_controller.asset_host = 'http://assets.example.com'
37 |
38 | # Specifies the header that your server uses for sending files.
39 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
40 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
41 |
42 | # Mount Action Cable outside main process or domain
43 | # config.action_cable.mount_path = nil
44 | # config.action_cable.url = 'wss://example.com/cable'
45 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
46 |
47 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
48 | # config.force_ssl = true
49 |
50 | # Use the lowest log level to ensure availability of diagnostic information
51 | # when problems arise.
52 | config.log_level = :debug
53 |
54 | # Prepend all log lines with the following tags.
55 | config.log_tags = [ :request_id ]
56 |
57 | # Use a different cache store in production.
58 | # config.cache_store = :mem_cache_store
59 |
60 | # Use a real queuing backend for Active Job (and separate queues per environment)
61 | # config.active_job.queue_adapter = :resque
62 | # config.active_job.queue_name_prefix = "workspace_#{Rails.env}"
63 | config.action_mailer.perform_caching = false
64 |
65 | # Ignore bad email addresses and do not raise email delivery errors.
66 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
67 | # config.action_mailer.raise_delivery_errors = false
68 |
69 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
70 | # the I18n.default_locale when a translation cannot be found).
71 | config.i18n.fallbacks = true
72 |
73 | # Send deprecation notices to registered listeners.
74 | config.active_support.deprecation = :silence
75 |
76 | # Use default logging formatter so that PID and timestamp are not suppressed.
77 | config.log_formatter = ::Logger::Formatter.new
78 |
79 | # Use a different logger for distributed setups.
80 | # require 'syslog/logger'
81 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
82 |
83 | if ENV['LOG_TO_STDOUT']
84 | logger = ActiveSupport::Logger.new(STDOUT)
85 | logger.formatter = config.log_formatter
86 | config.logger = ActiveSupport::TaggedLogging.new(logger)
87 | end
88 |
89 | # Do not dump schema after migrations.
90 | config.active_record.dump_schema_after_migration = false
91 | end
92 |
--------------------------------------------------------------------------------
/app/controllers/plex_services_controller.rb:
--------------------------------------------------------------------------------
1 | class PlexServicesController < ApplicationController
2 | before_action :set_plex_service, only: [:show, :edit, :update, :destroy]
3 | before_action :set_sidebar_values, except: [:now_playing, :recently_added, :create, :update, :destroy]
4 |
5 | def set_sidebar_values
6 | @services = Service.all
7 | @plex_services = PlexService.all
8 | # @weathers = Weather.all
9 | end
10 |
11 | def index
12 | end
13 |
14 | def edit
15 | end
16 |
17 | def show
18 | end
19 |
20 | def all_plex_services
21 | end
22 |
23 | # GET /now_playings/1
24 | # GET /now_playings/1.json
25 | def now_playing
26 | if params[:id].nil? && params[:plex_service_id]
27 | @plex_sessions = PlexService.find(params[:plex_service_id]).plex_sessions
28 | @active = ''
29 | respond_to do |format|
30 | format.html { render partial: 'plex_services/plex_session', collection: @plex_sessions }
31 | format.json { render json: @plex_sessions }
32 | end
33 | else
34 | @plex_session = PlexSession.find(params[:id])
35 | @active = ''
36 |
37 | if params[:active] && params[:active].casecmp('true').zero?
38 | @active = 'active'
39 | end
40 |
41 | respond_to do |format|
42 | format.html { render @plex_session }
43 | format.json { render @plex_session }
44 | end
45 | end
46 | end
47 |
48 | # GET /recently_addeds/1
49 | # GET /recently_addeds/1.json
50 | def recently_added
51 | @plex_recently_added = PlexRecentlyAdded.find(params[:id])
52 | @active = ''
53 |
54 | if params[:active] && params[:active].casecmp('true').zero?
55 | @active = 'active'
56 | end
57 |
58 | respond_to do |format|
59 | format.html { render @plex_recently_added }
60 | format.json { render @plex_recently_added }
61 | end
62 | end
63 |
64 |
65 | # need to use build_service!!!!! This article sparked my thoughts to use build
66 | # http://stackoverflow.com/questions/26458417/rails-polymorphic-posts-associations-and-form-for-in-views
67 |
68 | def new
69 | @plex_service = PlexService.new
70 | @service = @plex_service.build_service
71 | end
72 |
73 | def create
74 | @plex_service = PlexService.new(plex_service_params)
75 |
76 | respond_to do |format|
77 | if @plex_service.save
78 | format.html { redirect_to @plex_service, notice: 'Service was successfully created.' }
79 | format.json { render :show, status: :created, location: @plex_service }
80 | else
81 | format.html { render :new }
82 | format.json { render json: @plex_service.errors, status: :unprocessable_entity }
83 | end
84 | end
85 | end
86 |
87 |
88 | def update
89 | respond_to do |format|
90 | if @plex_service.update(plex_service_params)
91 | format.html { redirect_to @plex_service, notice: 'Plex Service was successfully updated.' }
92 | format.json { render :show, status: :ok, location: @plex_service }
93 | else
94 | format.html { render :edit }
95 | format.json { render json: @plex_service.errors, status: :unprocessable_entity }
96 | end
97 | end
98 | end
99 |
100 | # DELETE /services/1
101 | # DELETE /services/1.json
102 | def destroy
103 | @plex_service.destroy
104 | respond_to do |format|
105 | format.html { redirect_to root_url, notice: 'Plex Service was successfully destroyed.' }
106 | format.json { head :no_content }
107 | end
108 | end
109 |
110 |
111 | private
112 | # Use callbacks to share common setup or constraints between actions.
113 | def set_plex_service
114 | @plex_service = PlexService.find(params[:id])
115 | end
116 |
117 | # Never trust parameters from the scary internet, only allow the white list through.
118 | # Going off of this site as a guide: http://astockwell.com/blog/2014/03/polymorphic-associations-in-rails-4-devise/
119 | def plex_service_params
120 | params.require(:plex_service).permit(:username, :password, service_attributes: [:id, :name, :ip, :dns_name, :port, :url])
121 | end
122 | end
123 |
--------------------------------------------------------------------------------
/test/fixtures/JSON/plex_recently_added_movie.json:
--------------------------------------------------------------------------------
1 | {
2 | "MediaContainer": {
3 | "size": 50,
4 | "allowSync": false,
5 | "identifier": "com.plexapp.plugins.library",
6 | "mediaTagPrefix": "/system/bundle/media/flags/",
7 | "mediaTagVersion": 1481837967,
8 | "mixedParents": true,
9 | "Metadata": [
10 | {
11 | "allowSync": true,
12 | "librarySectionID": 1,
13 | "librarySectionTitle": "Movies",
14 | "librarySectionUUID": "d370c316-f79b-4810-b0cc-fad49bef2af7",
15 | "ratingKey": "49724",
16 | "key": "/library/metadata/49724",
17 | "studio": "Fox 2000 Pictures",
18 | "type": "movie",
19 | "title": "Hidden Figures",
20 | "contentRating": "PG",
21 | "summary": "The incredible untold story of Katherine G. Johnson, Dorothy Vaughan and Mary Jackson - brilliant African-American women working at NASA, who served as the brains behind one of the greatest operations in history: the launch of astronaut John Glenn into orbit, a stunning achievement that restored the nation's confidence, turned around the Space Race, and galvanized the world. The visionary trio crossed all gender and race lines to inspire generations to dream big.",
22 | "rating": 9.3,
23 | "audienceRating": 9.4,
24 | "year": 2017,
25 | "tagline": "Meet the women you don't know, behind the mission you do.",
26 | "thumb": "/library/metadata/49724/thumb/1490327440",
27 | "art": "/library/metadata/49724/art/1490327440",
28 | "duration": 7603062,
29 | "originallyAvailableAt": "2017-01-06",
30 | "addedAt": 1490327405,
31 | "updatedAt": 1490327440,
32 | "audienceRatingImage": "rottentomatoes://image.rating.upright",
33 | "chapterSource": "media",
34 | "primaryExtraKey": "/library/metadata/49725",
35 | "ratingImage": "rottentomatoes://image.rating.certified",
36 | "Media": [
37 | {
38 | "videoResolution": "1080",
39 | "id": 135653,
40 | "duration": 7603062,
41 | "bitrate": 11111,
42 | "width": 1920,
43 | "height": 808,
44 | "aspectRatio": 2.35,
45 | "audioChannels": 6,
46 | "audioCodec": "dca",
47 | "videoCodec": "h264",
48 | "container": "mkv",
49 | "videoFrameRate": "24p",
50 | "audioProfile": "dts",
51 | "videoProfile": "high",
52 | "Part": [
53 | {
54 | "id": 136430,
55 | "key": "/library/parts/136430/1490335125/file.mkv",
56 | "duration": 7603062,
57 | "file": "/mnt/PLEX-MEDIA/Movies/Hidden Figures (2016)/Hidden Figures.mkv",
58 | "size": 10559697658,
59 | "audioProfile": "dts",
60 | "container": "mkv",
61 | "deepAnalysisVersion": "2",
62 | "indexes": "sd",
63 | "requiredBandwidths": "42252,29243,22270,16875,12600,12600,12600,12600",
64 | "videoProfile": "high"
65 | }
66 | ]
67 | }
68 | ],
69 | "Genre": [
70 | {
71 | "tag": "Biography"
72 | },
73 | {
74 | "tag": "History"
75 | },
76 | {
77 | "tag": "Drama"
78 | }
79 | ],
80 | "Director": [
81 | {
82 | "tag": "Theodore Melfi"
83 | }
84 | ],
85 | "Writer": [
86 | {
87 | "tag": "Allison Schroeder"
88 | },
89 | {
90 | "tag": "Theodore Melfi"
91 | }
92 | ],
93 | "Country": [
94 | {
95 | "tag": "USA"
96 | }
97 | ],
98 | "Role": [
99 | {
100 | "tag": "Taraji P. Henson"
101 | },
102 | {
103 | "tag": "Octavia Spencer"
104 | },
105 | {
106 | "tag": "Janelle Monae"
107 | }
108 | ]
109 | }
110 | ]
111 | }
112 | }
--------------------------------------------------------------------------------
/app/controllers/notifications_controller.rb:
--------------------------------------------------------------------------------
1 | class NotificationsController < ApplicationController
2 | include ActionController::Live
3 | # How to do SSE properly:
4 | # https://github.com/rails/rails/blob/6061c540ac7880233a6e32de85cec72c20ed8778/actionpack/lib/action_controller/metal/live.rb#L23
5 |
6 | def notifications
7 | response.headers['Content-Type'] = 'text/event-stream'
8 | sse = SSE.new(response.stream, retry: 2000)
9 | i = 0
10 | begin
11 | loop do
12 | i+= 1
13 | events = Array.new
14 | @plex_services = PlexService.all
15 | @services = Service.all
16 | # @weathers = Weather.all
17 |
18 | if @plex_services.empty? && @services.empty?
19 | logger.info 'There were no PlexServices or Generic Services, sleeping for 10s.'
20 | sleep(10)
21 | end
22 |
23 | @plex_services.try(:each) do |plex_service|
24 | plex_service.update_plex_data
25 | all_active_sessions = []
26 | plex_service.plex_sessions.try(:each) do |plex_session|
27 | if plex_session.plex_object.nil?
28 | logger.error {"PlexSession: #{plex_session.id} had a nil plex_object. Destroying."}
29 | plex_session.destroy!
30 | next
31 | end
32 | all_active_sessions << {session_id: plex_session.id,
33 | html: render_to_string(partial: 'plex_services/now_playing',
34 | formats: [:html],
35 | locals: {plex_session: plex_session,
36 | active: ''}),
37 | progress: plex_session.get_percent_done,
38 | plex_service_id: plex_service.id,
39 | active_streams_html: render_to_string(partial: 'plex_services/now_playing_navbar',
40 | formats: [:html],
41 | locals: {plex_service: plex_service}),
42 | active_sessions: PlexSession.all.ids}
43 |
44 | end
45 | if !all_active_sessions.empty?
46 | all_active_sessions.each do |active_session|
47 | events << {data: active_session, event: 'plex_now_playing'}
48 | end
49 | elsif (i % 5).zero?
50 | events << {data: [], event: 'plex_now_playing'}
51 | end
52 | if (i % 10).zero?
53 | plex_service.plex_recently_addeds.order('added_date DESC').first(5).each do |pra|
54 | data = {id: pra.id,
55 | html: render_to_string(partial: 'plex_services/recently_added',
56 | formats: [:html],
57 | locals: {plex_recently_added: pra,
58 | active: ''})}
59 | events << {data: data, event: 'plex_recently_added'}
60 | end
61 | end
62 | end
63 |
64 | @services.try(:each) do |service|
65 | if (i % 5).zero?
66 | logger.debug('Looped 5 times, sending all service statuses.')
67 | service.ping
68 | events << {data: {id: service.id, html: render_to_string(partial: 'services/service', formats: [:html], locals: {service: service})}.to_json, event: 'online_status'}
69 | else
70 | unless service.ping_for_status_change.nil?
71 | events << {data: {id: service.id, html: render_to_string(partial: 'services/service', formats: [:html], locals: {service: service})}.to_json, event: 'online_status'}
72 | end
73 | end
74 | end
75 |
76 | # @weathers.try(:each) do |weather|
77 | # weather.get_weather
78 | # events << {data: weather, event: 'weather'}
79 | # end
80 |
81 | events.each do |e|
82 | sse.write(e[:data], event: e[:event])
83 | end
84 | sleep 2
85 | end
86 | rescue IOError
87 | logger.warn 'Stream closed: IO Error'
88 | rescue ClientDisconnected
89 | logger.warn 'Stream closed: Client Disconnect'
90 | # rescue StandardError => e
91 | # logger.error "An error occurred during the loop: #{e.message}"
92 | ensure
93 | sse.close
94 | end
95 | end
96 | end
97 |
--------------------------------------------------------------------------------
/app/models/service.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: services
4 | #
5 | # id :integer not null, primary key
6 | # name :string not null
7 | # dns_name :string
8 | # ip :string
9 | # url :string not null
10 | # port :integer not null
11 | # service_flavor_id :integer
12 | # service_flavor_type :string
13 | # created_at :datetime not null
14 | # updated_at :datetime not null
15 | #
16 |
17 | require 'resolv'
18 | class Service < ActiveRecord::Base
19 | belongs_to :service_flavor, polymorphic: :true, optional: true
20 | has_one :server_load
21 | # before_destroy :destroy_associated
22 | after_initialize :init
23 | # after_create :ping
24 | after_validation :clear_ping_cache!
25 |
26 | attr_accessor :timeout
27 | strip_attributes only: [:ip, :url, :dns_name], collapse_spaces: true
28 |
29 | validates_associated :service_flavor
30 | validates_presence_of :name
31 | validates_uniqueness_of :name
32 | validates_presence_of :url
33 | validates_uniqueness_of :url
34 | validates_inclusion_of :port, in: 1..65_535
35 | validates :ip,
36 | length: {minimum: 7, maximum: 45},
37 | format: {with: Resolv::IPv4::Regex},
38 | uniqueness: {scope: :port},
39 | allow_blank: true
40 | validates :dns_name,
41 | length: {minimum: 2, maximum: 127},
42 | uniqueness: {scope: :port},
43 | allow_blank: true
44 | validates :ip, presence: true, if: :ip_and_dns_name_dont_exist
45 | validates :dns_name, presence: true, if: :ip_and_dns_name_dont_exist
46 |
47 | def init
48 | @timeout ||= 3
49 | self.online_status ||= false
50 | end
51 |
52 | def ip_and_dns_name_dont_exist
53 | if (ip.blank? || ip.to_s.empty?) && (dns_name.blank? || dns_name.to_s.empty?)
54 | errors.add(:base, 'IP Address or DNS Name must exist')
55 | true
56 | else
57 | false
58 | end
59 | end
60 |
61 | def clear_ping_cache!
62 | if Rails.cache.delete("service_#{id}/online")
63 | logger.debug('Successfully deleted cache!')
64 | end
65 | self
66 | end
67 |
68 | def ping
69 | Rails.cache.fetch("service_#{id}/online", expires_in: 10.seconds, race_condition_ttl: 7.seconds) do
70 | check_online_status
71 | self.online_status
72 | end
73 | end
74 |
75 | def online_status_string
76 | online?(online_status)
77 | end
78 |
79 | def online?(boolean)
80 | boolean ? 'online' : 'offline'
81 | end
82 |
83 | def ping_for_status_change
84 | before_ping = self.online_status
85 | ping_result = ping
86 | if before_ping != ping_result
87 | logger.info("Detected status change from #{online?(before_ping)} to #{online_status_string} for service: #{name}")
88 | self.online_status
89 | else
90 | nil
91 | end
92 | end
93 |
94 | def connect_method
95 | if !dns_name.blank?
96 | dns_name
97 | else
98 | ip
99 | end
100 | end
101 |
102 | def as_json(options)
103 | json = super(only: [:id])
104 | json[:self_uri] = Rails.application.routes.url_helpers.service_online_status_path(id)
105 | json
106 | end
107 |
108 | def online!
109 | self.online_status = true
110 | self
111 | end
112 |
113 | def offline!
114 | self.online_status = false
115 | self
116 | end
117 |
118 | def last_seen_now!
119 | self.last_seen = Time.now
120 | self
121 | end
122 |
123 | def online_status
124 | Rails.cache.read("service/#{id}/online_status")
125 | end
126 |
127 | def online_status=(boolean_status)
128 | Rails.cache.write("service/#{id}/online_status", boolean_status)
129 | end
130 |
131 | def last_seen
132 | Rails.cache.read("service/#{id}/last_seen")
133 | end
134 |
135 | def last_seen=(timestamp)
136 | Rails.cache.write("service/#{id}/last_seen", timestamp)
137 | end
138 |
139 | private
140 |
141 | def check_online_status
142 | ping_destination = connect_method
143 | begin
144 | Timeout.timeout(@timeout) do
145 | s = TCPSocket.new(ping_destination, port)
146 | s.close
147 | online!
148 | last_seen_now!
149 | end
150 | #TODO: Use connection refused to indicate that the server itself is still responding.
151 | rescue Errno::ECONNREFUSED
152 | offline!
153 | rescue Timeout::Error, Errno::ENETUNREACH, Errno::EHOSTUNREACH, SocketError
154 | offline!
155 | end
156 | end
157 | end
158 |
--------------------------------------------------------------------------------
/serverSetup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 |
4 | case "$(uname -s)" in
5 |
6 | Darwin)
7 | OS="Mac OSX"
8 | ;;
9 |
10 | Linux)
11 | OS="Linux"
12 | ;;
13 |
14 | CYGWIN*|MINGW32*|MSYS*)
15 | OS="Windows"
16 | ;;
17 |
18 | *)
19 | OS="Unknown"
20 | ;;
21 | esac
22 |
23 | printf "\nThe current operating system was detected as: ${OS}."
24 |
25 | cd "$(dirname "$0")"
26 | if [[ "$OS" != "Windows" ]]; then
27 | printf "\nStopping server if it's running.\n"
28 | source stopServer.sh
29 | fi
30 |
31 | printf "\nDestroying tmp folder to clear caches and leftover pidfiles.\n"
32 | rm -rf tmp
33 |
34 | printf "\nDestroying public/images to clear leftover images.\n"
35 | rm -rf public/images
36 |
37 | HOME_RVM=$HOME/.rvm/scripts/rvm
38 | ROOT_RVM="/usr/local/rvm/scripts/rvm"
39 | UBUNTU_RVM="/usr/share/rvm/bin/rvm"
40 |
41 | RUBY_VERSION="2.4.1"
42 |
43 | # Load RVM into a shell session *as a function*
44 | if [[ -s ${HOME_RVM} ]] ; then
45 | # First try to load from a user install
46 | source ${HOME_RVM} \
47 | && printf "\nRVM successfully loaded from $HOME_RVM\n" \
48 | && printf "\nInstalling ruby-$RUBY_VERSION if it isn't already installed. This could take a while...\n" \
49 | && rvm install ruby-${RUBY_VERSION} \
50 | && rvm use gemset ruby-${RUBY_VERSION}@porthole
51 |
52 | elif [[ -s ${UBUNTU_RVM} ]] ; then
53 | # Second try to use RVM in an Ubuntu install
54 | printf "\nRVM should already be loaded from $UBUNTU_RVM\n" \
55 | && printf "\nInstalling ruby-$RUBY_VERSION if it isn't already installed. This could take a while...\n" \
56 | && rvm install ruby-${RUBY_VERSION} \
57 | && rvm use gemset ruby-${RUBY_VERSION}@porthole
58 |
59 | elif [[ -s ${ROOT_RVM} ]] ; then
60 | # Then try to load from a root install
61 | source ${ROOT_RVM} \
62 | && printf "\nRVM successfully loaded from $ROOT_RVM\n" \
63 | && printf "\nInstalling ruby-$RUBY_VERSION if it isn't already installed. This could take a while...\n" \
64 | && rvm install ruby-${RUBY_VERSION} \
65 | && rvm use gemset ruby-${RUBY_VERSION}@porthole
66 |
67 | else
68 | if [[ "$OS" == "Windows" ]]; then
69 | printf "\nUsing system Ruby because we're on Windows...\n"
70 | else
71 | printf "\nWARNING: A RVM installation was not found. Did you follow the instructions correctly? Attempting to use system Ruby...\n"
72 | fi
73 | fi
74 |
75 | INSTALLED_RUBY_VERSION="$(ruby -v)"
76 |
77 | if ! [[ ${INSTALLED_RUBY_VERSION} =~ 2\.[3-4]\.[0-9]+ ]]; then
78 | printf "\nERROR: The required version of Ruby was not installed. This application will not work with any Ruby < 2.3.x"
79 | printf "\nFound: ${INSTALLED_RUBY_VERSION}\n"
80 | exit 1
81 | fi
82 |
83 | gem install bundler rake --no-ri --no-rdoc
84 |
85 | printf "\nInstalling Gems. This could take a while depending on how powerful your CPU is...\n"
86 | bundle install --without development test
87 |
88 | EXAMPLE_WINDOWS_CONFIG_FILE="example.windows.server_config.yml"
89 | EXAMPLE_CONFIG_FILE="example.server_config.yml"
90 | SERVER_CONFIG_FILE="server_config.yml"
91 | SECRETS="config/secrets.yml"
92 | SECRET="$(rake secret)"
93 | BOGUS="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
94 |
95 | printf "\nRemoving old secrets.yml file if it exists. Creating a fresh one with new secrets.\n"
96 | if [ -f ${SECRETS} ] ; then
97 | rm ${SECRETS}
98 | fi
99 |
100 | echo -e "development:\n secret_key_base:" ${BOGUS} "\n" >> ${SECRETS}
101 | echo -e "test:\n secret_key_base:" ${BOGUS} "\n" >> ${SECRETS}
102 | echo -e "production:\n secret_key_base:" ${SECRET} >> ${SECRETS}
103 |
104 | if [ ! -f ${SERVER_CONFIG_FILE} ]; then
105 | if [[ "$OS" == "Windows" ]]; then
106 | printf "\nSince windows is being used we'll use 127.0.0.1:3000 as the default host."
107 | printf "\nYou'll need to configure server_config.yml in order to accept traffic from external hosts.\n"
108 | EXAMPLE_CONFIG_FILE=${EXAMPLE_WINDOWS_CONFIG_FILE}
109 | fi
110 |
111 | if cp ${EXAMPLE_CONFIG_FILE} ${SERVER_CONFIG_FILE} 2>&1; then
112 | printf "\nCreated server_config.yml since it didn't exist."
113 | fi
114 | else
115 | printf "\nserver_config.yml exists, not creating."
116 | fi
117 |
118 | printf "\nCreating and setting up the database for production.\n"
119 | bundle exec rake db:create RAILS_ENV=production
120 | bundle exec rake db:migrate RAILS_ENV=production
121 |
122 | printf "\nCompiling assets. This could take a while depending on CPU power...\n"
123 | bundle exec rake assets:clobber assets:precompile RAILS_ENV=production
124 |
125 | printf "\nCreating images directory.\n"
126 | mkdir -p public/images
127 |
128 | printf "\nFinished. Run ./startServer.sh to start the server!\n\n"
129 |
--------------------------------------------------------------------------------
/app/assets/javascripts/notification_handler.js.coffee.erb:
--------------------------------------------------------------------------------
1 | "<% require 'yaml' %>"
2 | # Yes, this will blow up if server_config.yml doesn't exist. Sucks to suck. RTFM
3 | source = new EventSource("<%= (YAML.load_file(Rails.root.join 'server_config.yml')['relative_root'] || '') + '/notifications' %>")
4 |
5 | ########### Service Online Status ############
6 |
7 | source.addEventListener 'online_status', (e) ->
8 | service = $.parseJSON(e.data)
9 | # console.log(service)
10 | $('#service_' + service.id).replaceWith(service.html)
11 |
12 | # $.get service.self_uri, (data) ->
13 | # $('#service_' + service.id).replaceWith(data)
14 | # console.log(data)
15 | # return
16 |
17 | ############# Plex Now Playing ################
18 |
19 | source.addEventListener 'plex_now_playing', (e) ->
20 | plex_now_playing_data = $.parseJSON(e.data)
21 | # console.log plex_now_playing_data
22 | # console.log "Plex - Now Playing - session #{plex_session.session_id}"
23 | # console.log $('[id^="streams_for_plex_service_"]').length && plex_now_playing_data.active_streams_html && plex_now_playing_data.active_streams_html.length
24 |
25 | if $('[id^="streams_for_plex_service_"]').length && plex_now_playing_data.active_streams_html && plex_now_playing_data.active_streams_html.length
26 | console.log "Updating Active Streams"
27 | $('#streams_for_plex_service_' + plex_now_playing_data.plex_service_id).replaceWith(plex_now_playing_data.active_streams_html)
28 | $('#streams_for_plex_service_' + plex_now_playing_data.plex_service_id).show()
29 | else if $('[id^="streams_for_plex_service_"]').length
30 | console.log "Hiding active stream"
31 | $('[id^="streams_for_plex_service_"]').hide()
32 | else if plex_now_playing_data.active_streams_html && plex_now_playing_data.active_streams_html.length
33 | console.log "Adding new active stream"
34 | $('#navbar_links').prepend('
' + plex_now_playing_data.active_streams_html + '
')
35 |
36 | updated_progressbar = """
37 |
43 | """
44 | if $('#plex_session_' + plex_now_playing_data.session_id).length
45 | # console.log "Updating progress bar"
46 | $('#plex_progressbar_' + plex_now_playing_data.session_id).replaceWith(updated_progressbar)
47 | else
48 | # console.log "Didn't find the element \"session #{plex_session.session_id}\""
49 | # console.log "Adding new session element.."
50 | if $("[id^=plex_recently_added_]")
51 | $("[id^=plex_recently_added_]").last().after(plex_now_playing_data.html)
52 | else if $("[id^=plex_session_]")
53 | $("[id^=plex_session_]").last().after(plex_now_playing_data.html)
54 | else
55 | $("[id^=carousel-inner]").append(plex_now_playing_data.html)
56 |
57 | #Find all the existing elements on the page and compare them to the active ids we got in the SSE
58 |
59 | #set every plex_session as stale so we can remove the sessions that aren't found in the active sessions from the server
60 | stale_sessions = $.find("[id^=plex_session_]")
61 | #iterate over known sessions
62 | if plex_now_playing_data.active_sessions
63 | for i in [0...plex_now_playing_data.active_sessions.length]
64 | #console.log "Active session is: plex_session_" + plex_session.active_sessions[i]
65 | for j in [0...stale_sessions.length]
66 | #console.log "Matching active session against: " + stale_sessions[j].id
67 | if stale_sessions[j].id == ("plex_session_" + plex_now_playing_data.active_sessions[i])
68 | #at position i, remove one element from the array
69 | stale_sessions.splice(j, 1)
70 | #console.log "Updated stale sessions length should be one less: " + stale_sessions.length
71 | break
72 | for k in [0...stale_sessions.length]
73 | #find the elements by id and remove them from the page
74 | if !$("##{stale_sessions[k].id}").hasClass("active") && !$("##{stale_sessions[k].id}").hasClass("next")
75 | #console.log "Stale element found, removing.."
76 | console.log "Removing element " + stale_sessions[k].id
77 | $("#" + stale_sessions[k].id).remove()
78 |
79 | source.addEventListener 'plex_recently_added', (e) ->
80 | pra = $.parseJSON(e.data)
81 | if $('div[id^=plex_session_]').length < 3
82 | #console.log "There were less than 3 plex_sessions, adding plex recently added"
83 | if $('#plex_recently_added_' + pra.id).length
84 | #console.log "The PRA element was found. Not adding it again."
85 | else
86 | console.log "ID of new pra: #{pra.id}"
87 |
88 | if $("[id^=plex_session_]")
89 | $("[id^=plex_session_]").last().after(pra.html)
90 | else if $("[id^=plex_recently_added_]")
91 | $("[id^=plex_recently_added_]").last().after(pra.html)
92 | else
93 | $("[id^=carousel-inner]").append(pra.html)
94 | # else
95 | # console.log "There were more than three PlexSessions, not showing RecentlyAdded"
96 |
97 |
98 | ############# Weather ################
99 |
100 | source.addEventListener 'weathers', (e) ->
101 | weather = $.parseJSON(e.data)
102 | console.log("Here's the weather object id we got: " + weather.id)
103 | console.log(weather)
104 | $.get weather.self_uri, (data) ->
105 | $('#weather_' + weather.id).replaceWith(data)
106 | console.log(data)
107 | return
--------------------------------------------------------------------------------