-
Notifications
You must be signed in to change notification settings - Fork 2
/
server.rb
135 lines (102 loc) · 4.87 KB
/
server.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
##
## Copyright (C) 2024 Jordan Ritter <[email protected]>
##
## WebCal server for Tides & Sunset information. Meant to replace
## sailwx.info/tides.mobilegeographics.com, which as of 2021 appears no longer
## to work.
##
# FIXME: fix tide event URLs to reference the right day from tz (not GMT)
require 'logger'
$LOG = Logger.new(STDOUT).tap do |log|
log.formatter = proc { |s, d, _, m| "#{d.strftime("%Y-%m-%d %H:%M:%S")} #{s} #{m}\n" }
end
require 'bundler/setup'
Bundler.require
require_relative 'webcaltides'
class Server < ::Sinatra::Base
set :app_file, File.expand_path(__FILE__)
set :root, File.expand_path(File.dirname(__FILE__))
set :cache_dir, settings.root + '/cache'
set :public_folder, settings.root + '/public'
set :views, settings.root + '/views'
set :static, true
configure do
enable :show_exceptions
disable :sessions, :logging
helpers ::Rack::Utils
Timezone::Lookup.config(:geonames) do |c|
c.username = ENV['USER']
end
FileUtils.mkdir_p settings.cache_dir
end
configure :development do
set :logging, $LOG
end
##
## URL entry points
##
get "/" do
erb :index, locals: { searchtext: nil, units: nil }
end
post "/" do
# Depending on your font these quotes may look the same -- but they're not
radius = params['within']
radius_units = params['units'] == 'metric' ? 'km' : 'mi'
searchtext = params['searchtext'].downcase.tr('“”', '""') rescue ''
# If we see anything like "42.1234, 1234.0132" then treat it like a GPS search
if ((lat, long) = WebCalTides.parse_gps(searchtext))
how = "near"
tokens = [lat, long]
radius ||= "10" # default;
tide_results = WebCalTides.find_tide_stations_by_gps(lat, long, within:radius, units: radius_units)
current_results = WebCalTides.find_current_stations_by_gps(lat, long, within:radius, units: radius_units)
else
how = "by"
# Parse search terms. Matched quotes are taken as-is (still
# lowercased), while everything else is tokenized via [ ,]+.
tokens = searchtext.scan(/["]([^"]+)["]/).flatten
searchtext.gsub!(/["]([^"]+)["]/, '')
tokens += searchtext.split(/[, ]+/).reject(&:empty?)
tide_results = WebCalTides.find_tide_stations(by:tokens, within:radius, units: radius_units)
current_results = WebCalTides.find_current_stations(by:tokens, within:radius, units: radius_units)
end
tide_results ||= []
current_results ||= []
for_what = "#{tokens}"
for_what += " within [#{radius}]" if radius
$LOG.info "search #{how} #{for_what} yields #{tide_results.count + current_results.count} results"
erb :index, locals: { searchtext: escape_html(searchtext || "Station..."),
tide_results: tide_results, current_results: current_results,
searchtokens: tokens, units: escape_html(params['units'])
}
end
# For currents, station can be either an ID (we'll use the first bin) or a BID (specific bin)
get "/:type/:station.ics" do
type = params[:type]
id = params[:station]
date = Date.parse(params[:date]) rescue Time.current.utc # e.g. 20231201, for utility but unsupported in UI
units = params[:units] || 'imperial'
no_solar = ["0", "false"].include?(params[:solar])
stamp = date.utc.strftime("%Y%m")
version = type == "currents" ? DataModels::CurrentData.version : DataModels::TideData.version
cached_ics = "#{settings.cache_dir}/#{type}_v#{version}_#{id}_#{stamp}_#{units}_#{no_solar ?"0":"1"}.ics"
# NOTE: Changed my mind on retval's. In the shit-fucked-up case, we end up sending out a
# full stack trace + 500, so really if we muck something up internally we should let the
# exception float up. Then we can assume that if we arrive without a calendar, then it's
# simply not there -> 404.
ics = File.read cached_ics rescue begin
calendar = case type
when "tides" then WebCalTides.tide_calendar_for(id, around: date, units: units) or halt 404
when "currents" then WebCalTides.current_calendar_for(id, around: date) or halt 404
else halt 404
end
WebCalTides.solar_calendar_for(calendar, around:date) unless no_solar
calendar.publish
$LOG.info "caching to #{cached_ics}"
File.write cached_ics, ical = calendar.to_ical
ical
end
content_type 'text/calendar', charset: 'utf-8'
body ics
end
end