Add URI ser/des for user Preferences

This commit is contained in:
Samantaz Fox 2021-08-31 11:07:34 +02:00
parent 555de5f4ef
commit 0989fe85d1
No known key found for this signature in database
GPG Key ID: F42821059186176E
1 changed files with 211 additions and 1 deletions

View File

@ -6,6 +6,7 @@
require "json"
require "yaml"
require "html"
require "uri/params"
#
# Enumerates types and constants
@ -85,21 +86,34 @@ end
#
class Preferences
# Annotation for URL parameters metadata storage
# All the properties below can have one, or more associated URL parameter(s):
# - If no "short" nor "long" parameter are specified, the URL parameter
# will be the property name stringified.
# - Otherwise, a short and a long parameter must be specified
# - An optional "compat" parameter can be provided.
annotation AssociatedURLParams; end
include JSON::Serializable
include YAML::Serializable
property annotations : Bool = false
property annotations_subscribed : Bool = false
@[AssociatedURLParams(short: "ap", long: "autoplay")]
property autoplay : Bool = false
@[AssociatedURLParams(short: "autoredir", long: "auto_redirect")]
property automatic_instance_redirect : Bool = false
@[JSON::Field(converter: Preferences::StringToArray)]
@[YAML::Field(converter: Preferences::StringToArray)]
@[AssociatedURLParams(short: "cap", long: "captions")]
property captions : Array(String) = ["", "", ""]
@[JSON::Field(converter: Preferences::StringToArray)]
@[YAML::Field(converter: Preferences::StringToArray)]
@[AssociatedURLParams(short: "coms", long: "comments")]
property comments : Array(String) = ["youtube", ""]
property continue : Bool = false
@ -107,24 +121,32 @@ class Preferences
@[JSON::Field(converter: Settings::Converters::Generic(Settings::Themes))]
@[YAML::Field(converter: Settings::Converters::Generic(Settings::Themes))]
@[AssociatedURLParams(long: "theme", compat: "dark_mode")]
property dark_mode : Settings::Themes = Settings::Themes::Dark
property latest_only : Bool = false
property listen : Bool = false
property local : Bool = false
@[AssociatedURLParams(short: "vr", long: "vr_mode")]
property vr_mode : Bool = true
@[AssociatedURLParams(short: "nick", long: "show_nick")]
property show_nick : Bool = true
@[JSON::Field(converter: Preferences::ProcessString)]
@[AssociatedURLParams(short: "hl", long: "locale")]
property locale : String = "en-US"
@[JSON::Field(converter: Preferences::ClampInt)]
property max_results : Int32 = 40
@[AssociatedURLParams(short: "notifs_only", long: "notifications_only")]
property notifications_only : Bool = false
@[JSON::Field(converter: Settings::Converters::Generic(Settings::PlayerStyles))]
@[YAML::Field(converter: Settings::Converters::Generic(Settings::PlayerStyles))]
@[AssociatedURLParams(short: "ps", long: "player_style")]
property player_style : Settings::PlayerStyles = Settings::PlayerStyles::Invidious
@[JSON::Field(converter: Preferences::ProcessString)]
@ -134,10 +156,12 @@ class Preferences
@[JSON::Field(converter: Settings::Converters::Generic(Settings::AnyHomePages))]
@[YAML::Field(converter: Settings::Converters::Generic(Settings::AnyHomePages))]
@[AssociatedURLParams(short: "home", long: "default_home")]
property default_home : Settings::AnyHomePages = Settings::HomePages::Popular
@[JSON::Field(converter: Settings::Converters::Generic(Array(Settings::AnyHomePages)))]
@[YAML::Field(converter: Settings::Converters::Generic(Array(Settings::AnyHomePages)))]
@[AssociatedURLParams(short: "menu", long: "feed_menu")]
property feed_menu : Array(Settings::AnyHomePages) = [
Settings::HomePages::Popular,
Settings::HomePages::Trending,
@ -155,9 +179,14 @@ class Preferences
property thin_mode : Bool = false
property unseen_only : Bool = false
@[AssociatedURLParams(short: "loop", long: "video_loop")]
property video_loop : Bool = false
@[AssociatedURLParams(short: "xd", long: "extend_desc")]
property extend_desc : Bool = false
@[AssociatedURLParams(short: "vol", long: "volume")]
property volume : Int32 = 100
def initialize
@ -202,6 +231,162 @@ class Preferences
@dark_mode = (old.dark?) ? Settings::Themes::Light : Settings::Themes::Dark
end
# From/To URI parameters
def self.from_uri(uri : String) : Preferences
return from_uri(URI::Params.parse(uri))
end
def self.from_uri(uri : URI::Params) : Preferences
prefs = Preferences.new
{% begin %}
{% for ivar in @type.instance_vars %}
{%
uri_ann = ivar.annotation(::Preferences::AssociatedURLParams)
default = ivar.default_value
max_idx = (ivar.type < Array) ? default.size - 1 : nil
has_long = (uri_ann && uri_ann[:long])
has_short = (uri_ann && uri_ann[:short])
has_compat = (uri_ann && uri_ann[:compat])
param_long = has_long ? uri_ann[:long] : ivar.id.stringify
param_short = has_short ? uri_ann[:short] : ivar.id.stringify
%}
# Arrays in HTTP forms are not standardized, so we have to
# support those manually here
{% if max_idx %}
temp = [] of String | Nil
(0..{{max_idx}}).each do |i|
temp_val = uri[{{param_long}}+"[#{i}]"]? || uri[{{param_short}}+"[#{i}]"]?
# If "compat" is available and nothing was found above, also check that
{% if has_compat %}
temp_val = uri[{{uri_ann[:compat]}}+"[#{i}]"]? if !temp_val
{% end %}
temp << temp_val
end
{% else %}
temp = uri[{{param_long}}]? || uri[{{param_short}}]?
# If "compat" is available and nothing was found above, also check that
{% if has_compat %}
temp = uri[{{uri_ann[:compat]}}]? if !temp
{% end %}
{% end %}
# Then, use the right converter and assign the variable
# We use a bunch of if/elsif/else statements because case/when can't
# be used in macros.
{% if ivar.type == Settings::Themes %}
val = Settings::Converters::Theme.from_s(temp)
{% elsif ivar.type == Enum %}
val = {{ivar.type}}.parse?(temp)
{% elsif ivar.type == Array(Enum) %}
val = [] of {{ivar.type}}
temp.compact.each { |item| val << {{ivar.type}}.parse?(item) }
{% elsif ivar.type == Bool %}
val = Settings::Converters::Boolean.from_s(temp)
{% elsif ivar.type == String || ivar.type == Array(String) %}
val = temp
{% elsif ivar.type == Float32 %}
val = temp.try &.as(String).to_f32?
{% else %}
val = nil
{% end %}
{% if max_idx %}
prefs.{{ivar.id}} = val.compact if val
{% else %}
prefs.{{ivar.id}} = val if val
{% end %}
{% end %}
{% end %}
return prefs
end
def to_uri(*, use_short = false, include_defaults = false) : URI::Params
params_raw = {} of String => Array(String)
# Hash(String, Array(String)))
{% for ivar in @type.instance_vars %}
{%
uri_ann = ivar.annotation(::Preferences::AssociatedURLParams)
has_long = (uri_ann && uri_ann[:long])
has_short = (uri_ann && uri_ann[:short])
param_long = has_long ? uri_ann[:long] : ivar.id.stringify
param_short = has_short ? uri_ann[:short] : param_long
%}
# If 'include_defaults' is false, check that the current
# value differs from the default
if include_defaults || @{{ivar.id}} != {{ivar.default_value}}
param_name = use_short ? {{param_short}} : {{param_long}}
{% if ivar.type == Bool %}
# Boolean is kinda special. We want to use 'true/false' for the
# long form and '0/1' for the short form
temp = Settings::Converters::Boolean.to_s(@{{ivar.id}}, numeric: use_short)
params_raw[param_name] = [temp]
{% elsif ivar.type < Array %}
# Arrays. Use `#to_s` if not already a string.
@{{ivar.id}}.each_with_index do |value, idx|
{% if ivar.type == Array(String) %}
params_raw[param_name + "[#{idx}]"] = [value]
{% else %}
params_raw[param_name + "[#{idx}]"] = [value.to_s]
{% end %}
end
{% else %}
# Same as aboce, but with scalars
{% if ivar.type == String %}
params_raw[param_name] = [@{{ivar.id}}]
{% else %}
params_raw[param_name] = [@{{ivar.id}}.to_s]
{% end %}
{% end %}
end
{% end %}
return URI::Params.new(params_raw)
end
# volume = env.params.body["volume"]?.try &.as(String).to_i?
# volume ||= CONFIG.default_user_preferences.volume
# comments = [] of String
# 2.times do |i|
# comments << (env.params.body["comments[#{i}]"]?.try &.as(String) || CONFIG.default_user_preferences.comments[i])
# end
# captions = [] of String
# 3.times do |i|
# captions << (env.params.body["captions[#{i}]"]?.try &.as(String) || CONFIG.default_user_preferences.captions[i])
# end
# default_home = env.params.body["default_home"]?.try { |x| Settings::HomePages.parse?(x) || Settings::UserHomePages.parse?(x) }
# default_home = CONFIG.default_user_preferences.default_home.to_s if !default_home
# feed_menu = [] of String
# 4.times do |index|
# option = env.params.body["feed_menu[#{index}]"]?.try &.as(String) || ""
# if !option.empty?
# feed_menu << option
# end
# end
# Old converters (TODO: remove or refactor them)
module ClampInt
def self.to_json(value : Int32, json : JSON::Builder)
json.number value
@ -393,6 +578,31 @@ module Settings::Converters
end
end # module theme
#
# String to Boolean conversion
#
module Boolean
extend self
def from_s(input : String?) : Bool?
return if !input
case input.downcase
when "0", "off", "false"; false
when "1", "on", "true" ; true
else
nil # invalid input, do nothing.
end
end
def to_s(input : Bool, numeric = false) : String
if numeric
return input ? "1" : "0"
else
return input ? "true" : "false"
end
end
end
end # module Settings::Converters
struct VideoPreferences