mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
Remove wpi::ArrayRef std::initializer_list constructor (#1745)
This can be dangerous as it refers to a temporary, and GCC 9.0 warns about its use. Instead add std::initializer_list overloads to common places it was used in an initializer_list sense.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
|
||||
/* Copyright (c) 2017-2019 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -176,6 +177,23 @@ class NetworkTableEntry final {
|
||||
*/
|
||||
std::vector<int> GetBooleanArray(ArrayRef<int> defaultValue) const;
|
||||
|
||||
/**
|
||||
* Gets the entry's value as a boolean array. If the entry does not exist
|
||||
* or is of different type, it will return the default value.
|
||||
*
|
||||
* @param defaultValue the value to be returned if no value is found
|
||||
* @return the entry's value or the given default value
|
||||
*
|
||||
* @note This makes a copy of the array. If the overhead of this is a
|
||||
* concern, use GetValue() instead.
|
||||
*
|
||||
* @note The returned array is std::vector<int> instead of std::vector<bool>
|
||||
* because std::vector<bool> is special-cased in C++. 0 is false, any
|
||||
* non-zero value is true.
|
||||
*/
|
||||
std::vector<int> GetBooleanArray(
|
||||
std::initializer_list<int> defaultValue) const;
|
||||
|
||||
/**
|
||||
* Gets the entry's value as a double array. If the entry does not exist
|
||||
* or is of different type, it will return the default value.
|
||||
@@ -188,6 +206,19 @@ class NetworkTableEntry final {
|
||||
*/
|
||||
std::vector<double> GetDoubleArray(ArrayRef<double> defaultValue) const;
|
||||
|
||||
/**
|
||||
* Gets the entry's value as a double array. If the entry does not exist
|
||||
* or is of different type, it will return the default value.
|
||||
*
|
||||
* @param defaultValue the value to be returned if no value is found
|
||||
* @return the entry's value or the given default value
|
||||
*
|
||||
* @note This makes a copy of the array. If the overhead of this is a
|
||||
* concern, use GetValue() instead.
|
||||
*/
|
||||
std::vector<double> GetDoubleArray(
|
||||
std::initializer_list<double> defaultValue) const;
|
||||
|
||||
/**
|
||||
* Gets the entry's value as a string array. If the entry does not exist
|
||||
* or is of different type, it will return the default value.
|
||||
@@ -201,6 +232,19 @@ class NetworkTableEntry final {
|
||||
std::vector<std::string> GetStringArray(
|
||||
ArrayRef<std::string> defaultValue) const;
|
||||
|
||||
/**
|
||||
* Gets the entry's value as a string array. If the entry does not exist
|
||||
* or is of different type, it will return the default value.
|
||||
*
|
||||
* @param defaultValue the value to be returned if no value is found
|
||||
* @return the entry's value or the given default value
|
||||
*
|
||||
* @note This makes a copy of the array. If the overhead of this is a
|
||||
* concern, use GetValue() instead.
|
||||
*/
|
||||
std::vector<std::string> GetStringArray(
|
||||
std::initializer_list<std::string> defaultValue) const;
|
||||
|
||||
/**
|
||||
* Sets the entry's value if it does not exist.
|
||||
*
|
||||
@@ -249,6 +293,14 @@ class NetworkTableEntry final {
|
||||
*/
|
||||
bool SetDefaultBooleanArray(ArrayRef<int> defaultValue);
|
||||
|
||||
/**
|
||||
* Sets the entry's value if it does not exist.
|
||||
*
|
||||
* @param defaultValue the default value to set
|
||||
* @return False if the entry exists with a different type
|
||||
*/
|
||||
bool SetDefaultBooleanArray(std::initializer_list<int> defaultValue);
|
||||
|
||||
/**
|
||||
* Sets the entry's value if it does not exist.
|
||||
*
|
||||
@@ -257,6 +309,14 @@ class NetworkTableEntry final {
|
||||
*/
|
||||
bool SetDefaultDoubleArray(ArrayRef<double> defaultValue);
|
||||
|
||||
/**
|
||||
* Sets the entry's value if it does not exist.
|
||||
*
|
||||
* @param defaultValue the default value to set
|
||||
* @return False if the entry exists with a different type
|
||||
*/
|
||||
bool SetDefaultDoubleArray(std::initializer_list<double> defaultValue);
|
||||
|
||||
/**
|
||||
* Sets the entry's value if it does not exist.
|
||||
*
|
||||
@@ -265,6 +325,14 @@ class NetworkTableEntry final {
|
||||
*/
|
||||
bool SetDefaultStringArray(ArrayRef<std::string> defaultValue);
|
||||
|
||||
/**
|
||||
* Sets the entry's value if it does not exist.
|
||||
*
|
||||
* @param defaultValue the default value to set
|
||||
* @return False if the entry exists with a different type
|
||||
*/
|
||||
bool SetDefaultStringArray(std::initializer_list<std::string> defaultValue);
|
||||
|
||||
/**
|
||||
* Sets the entry's value.
|
||||
*
|
||||
@@ -305,6 +373,22 @@ class NetworkTableEntry final {
|
||||
*/
|
||||
bool SetRaw(StringRef value);
|
||||
|
||||
/**
|
||||
* Sets the entry's value.
|
||||
*
|
||||
* @param value the value to set
|
||||
* @return False if the entry exists with a different type
|
||||
*/
|
||||
bool SetBooleanArray(ArrayRef<bool> value);
|
||||
|
||||
/**
|
||||
* Sets the entry's value.
|
||||
*
|
||||
* @param value the value to set
|
||||
* @return False if the entry exists with a different type
|
||||
*/
|
||||
bool SetBooleanArray(std::initializer_list<bool> value);
|
||||
|
||||
/**
|
||||
* Sets the entry's value.
|
||||
*
|
||||
@@ -313,6 +397,14 @@ class NetworkTableEntry final {
|
||||
*/
|
||||
bool SetBooleanArray(ArrayRef<int> value);
|
||||
|
||||
/**
|
||||
* Sets the entry's value.
|
||||
*
|
||||
* @param value the value to set
|
||||
* @return False if the entry exists with a different type
|
||||
*/
|
||||
bool SetBooleanArray(std::initializer_list<int> value);
|
||||
|
||||
/**
|
||||
* Sets the entry's value.
|
||||
*
|
||||
@@ -321,6 +413,14 @@ class NetworkTableEntry final {
|
||||
*/
|
||||
bool SetDoubleArray(ArrayRef<double> value);
|
||||
|
||||
/**
|
||||
* Sets the entry's value.
|
||||
*
|
||||
* @param value the value to set
|
||||
* @return False if the entry exists with a different type
|
||||
*/
|
||||
bool SetDoubleArray(std::initializer_list<double> value);
|
||||
|
||||
/**
|
||||
* Sets the entry's value.
|
||||
*
|
||||
@@ -329,6 +429,14 @@ class NetworkTableEntry final {
|
||||
*/
|
||||
bool SetStringArray(ArrayRef<std::string> value);
|
||||
|
||||
/**
|
||||
* Sets the entry's value.
|
||||
*
|
||||
* @param value the value to set
|
||||
* @return False if the entry exists with a different type
|
||||
*/
|
||||
bool SetStringArray(std::initializer_list<std::string> value);
|
||||
|
||||
/**
|
||||
* Sets the entry's value. If the value is of different type, the type is
|
||||
* changed to match the new value.
|
||||
@@ -369,6 +477,22 @@ class NetworkTableEntry final {
|
||||
*/
|
||||
void ForceSetRaw(StringRef value);
|
||||
|
||||
/**
|
||||
* Sets the entry's value. If the value is of different type, the type is
|
||||
* changed to match the new value.
|
||||
*
|
||||
* @param value the value to set
|
||||
*/
|
||||
void ForceSetBooleanArray(ArrayRef<bool> value);
|
||||
|
||||
/**
|
||||
* Sets the entry's value. If the value is of different type, the type is
|
||||
* changed to match the new value.
|
||||
*
|
||||
* @param value the value to set
|
||||
*/
|
||||
void ForceSetBooleanArray(std::initializer_list<bool> value);
|
||||
|
||||
/**
|
||||
* Sets the entry's value. If the value is of different type, the type is
|
||||
* changed to match the new value.
|
||||
@@ -377,6 +501,14 @@ class NetworkTableEntry final {
|
||||
*/
|
||||
void ForceSetBooleanArray(ArrayRef<int> value);
|
||||
|
||||
/**
|
||||
* Sets the entry's value. If the value is of different type, the type is
|
||||
* changed to match the new value.
|
||||
*
|
||||
* @param value the value to set
|
||||
*/
|
||||
void ForceSetBooleanArray(std::initializer_list<int> value);
|
||||
|
||||
/**
|
||||
* Sets the entry's value. If the value is of different type, the type is
|
||||
* changed to match the new value.
|
||||
@@ -385,6 +517,14 @@ class NetworkTableEntry final {
|
||||
*/
|
||||
void ForceSetDoubleArray(ArrayRef<double> value);
|
||||
|
||||
/**
|
||||
* Sets the entry's value. If the value is of different type, the type is
|
||||
* changed to match the new value.
|
||||
*
|
||||
* @param value the value to set
|
||||
*/
|
||||
void ForceSetDoubleArray(std::initializer_list<double> value);
|
||||
|
||||
/**
|
||||
* Sets the entry's value. If the value is of different type, the type is
|
||||
* changed to match the new value.
|
||||
@@ -393,6 +533,14 @@ class NetworkTableEntry final {
|
||||
*/
|
||||
void ForceSetStringArray(ArrayRef<std::string> value);
|
||||
|
||||
/**
|
||||
* Sets the entry's value. If the value is of different type, the type is
|
||||
* changed to match the new value.
|
||||
*
|
||||
* @param value the value to set
|
||||
*/
|
||||
void ForceSetStringArray(std::initializer_list<std::string> value);
|
||||
|
||||
/**
|
||||
* Sets flags.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2017. All Rights Reserved. */
|
||||
/* Copyright (c) FIRST 2017-2019. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
@@ -76,6 +76,12 @@ inline std::vector<int> NetworkTableEntry::GetBooleanArray(
|
||||
return value->GetBooleanArray();
|
||||
}
|
||||
|
||||
inline std::vector<int> NetworkTableEntry::GetBooleanArray(
|
||||
std::initializer_list<int> defaultValue) const {
|
||||
return GetBooleanArray(
|
||||
wpi::makeArrayRef(defaultValue.begin(), defaultValue.end()));
|
||||
}
|
||||
|
||||
inline std::vector<double> NetworkTableEntry::GetDoubleArray(
|
||||
ArrayRef<double> defaultValue) const {
|
||||
auto value = GetEntryValue(m_handle);
|
||||
@@ -83,6 +89,12 @@ inline std::vector<double> NetworkTableEntry::GetDoubleArray(
|
||||
return value->GetDoubleArray();
|
||||
}
|
||||
|
||||
inline std::vector<double> NetworkTableEntry::GetDoubleArray(
|
||||
std::initializer_list<double> defaultValue) const {
|
||||
return GetDoubleArray(
|
||||
wpi::makeArrayRef(defaultValue.begin(), defaultValue.end()));
|
||||
}
|
||||
|
||||
inline std::vector<std::string> NetworkTableEntry::GetStringArray(
|
||||
ArrayRef<std::string> defaultValue) const {
|
||||
auto value = GetEntryValue(m_handle);
|
||||
@@ -90,6 +102,12 @@ inline std::vector<std::string> NetworkTableEntry::GetStringArray(
|
||||
return value->GetStringArray();
|
||||
}
|
||||
|
||||
inline std::vector<std::string> NetworkTableEntry::GetStringArray(
|
||||
std::initializer_list<std::string> defaultValue) const {
|
||||
return GetStringArray(
|
||||
wpi::makeArrayRef(defaultValue.begin(), defaultValue.end()));
|
||||
}
|
||||
|
||||
inline bool NetworkTableEntry::SetDefaultValue(std::shared_ptr<Value> value) {
|
||||
return SetDefaultEntryValue(m_handle, value);
|
||||
}
|
||||
@@ -145,18 +163,42 @@ inline bool NetworkTableEntry::SetRaw(StringRef value) {
|
||||
return SetEntryValue(m_handle, Value::MakeRaw(value));
|
||||
}
|
||||
|
||||
inline bool NetworkTableEntry::SetBooleanArray(ArrayRef<bool> value) {
|
||||
return SetEntryValue(m_handle, Value::MakeBooleanArray(value));
|
||||
}
|
||||
|
||||
inline bool NetworkTableEntry::SetBooleanArray(
|
||||
std::initializer_list<bool> value) {
|
||||
return SetEntryValue(m_handle, Value::MakeBooleanArray(value));
|
||||
}
|
||||
|
||||
inline bool NetworkTableEntry::SetBooleanArray(ArrayRef<int> value) {
|
||||
return SetEntryValue(m_handle, Value::MakeBooleanArray(value));
|
||||
}
|
||||
|
||||
inline bool NetworkTableEntry::SetBooleanArray(
|
||||
std::initializer_list<int> value) {
|
||||
return SetEntryValue(m_handle, Value::MakeBooleanArray(value));
|
||||
}
|
||||
|
||||
inline bool NetworkTableEntry::SetDoubleArray(ArrayRef<double> value) {
|
||||
return SetEntryValue(m_handle, Value::MakeDoubleArray(value));
|
||||
}
|
||||
|
||||
inline bool NetworkTableEntry::SetDoubleArray(
|
||||
std::initializer_list<double> value) {
|
||||
return SetEntryValue(m_handle, Value::MakeDoubleArray(value));
|
||||
}
|
||||
|
||||
inline bool NetworkTableEntry::SetStringArray(ArrayRef<std::string> value) {
|
||||
return SetEntryValue(m_handle, Value::MakeStringArray(value));
|
||||
}
|
||||
|
||||
inline bool NetworkTableEntry::SetStringArray(
|
||||
std::initializer_list<std::string> value) {
|
||||
return SetEntryValue(m_handle, Value::MakeStringArray(value));
|
||||
}
|
||||
|
||||
inline void NetworkTableEntry::ForceSetValue(std::shared_ptr<Value> value) {
|
||||
SetEntryTypeValue(m_handle, value);
|
||||
}
|
||||
@@ -177,19 +219,43 @@ inline void NetworkTableEntry::ForceSetRaw(StringRef value) {
|
||||
SetEntryTypeValue(m_handle, Value::MakeRaw(value));
|
||||
}
|
||||
|
||||
inline void NetworkTableEntry::ForceSetBooleanArray(ArrayRef<bool> value) {
|
||||
SetEntryTypeValue(m_handle, Value::MakeBooleanArray(value));
|
||||
}
|
||||
|
||||
inline void NetworkTableEntry::ForceSetBooleanArray(
|
||||
std::initializer_list<bool> value) {
|
||||
SetEntryTypeValue(m_handle, Value::MakeBooleanArray(value));
|
||||
}
|
||||
|
||||
inline void NetworkTableEntry::ForceSetBooleanArray(ArrayRef<int> value) {
|
||||
SetEntryTypeValue(m_handle, Value::MakeBooleanArray(value));
|
||||
}
|
||||
|
||||
inline void NetworkTableEntry::ForceSetBooleanArray(
|
||||
std::initializer_list<int> value) {
|
||||
SetEntryTypeValue(m_handle, Value::MakeBooleanArray(value));
|
||||
}
|
||||
|
||||
inline void NetworkTableEntry::ForceSetDoubleArray(ArrayRef<double> value) {
|
||||
SetEntryTypeValue(m_handle, Value::MakeDoubleArray(value));
|
||||
}
|
||||
|
||||
inline void NetworkTableEntry::ForceSetDoubleArray(
|
||||
std::initializer_list<double> value) {
|
||||
SetEntryTypeValue(m_handle, Value::MakeDoubleArray(value));
|
||||
}
|
||||
|
||||
inline void NetworkTableEntry::ForceSetStringArray(
|
||||
ArrayRef<std::string> value) {
|
||||
SetEntryTypeValue(m_handle, Value::MakeStringArray(value));
|
||||
}
|
||||
|
||||
inline void NetworkTableEntry::ForceSetStringArray(
|
||||
std::initializer_list<std::string> value) {
|
||||
SetEntryTypeValue(m_handle, Value::MakeStringArray(value));
|
||||
}
|
||||
|
||||
inline void NetworkTableEntry::SetFlags(unsigned int flags) {
|
||||
SetEntryFlags(m_handle, GetFlags() | flags);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
@@ -374,6 +375,20 @@ class Value final {
|
||||
static std::shared_ptr<Value> MakeBooleanArray(ArrayRef<bool> value,
|
||||
uint64_t time = 0);
|
||||
|
||||
/**
|
||||
* Creates a boolean array entry value.
|
||||
*
|
||||
* @param value the value
|
||||
* @param time if nonzero, the creation time to use (instead of the current
|
||||
* time)
|
||||
* @return The entry value
|
||||
*/
|
||||
static std::shared_ptr<Value> MakeBooleanArray(
|
||||
std::initializer_list<bool> value, uint64_t time = 0) {
|
||||
return MakeBooleanArray(wpi::makeArrayRef(value.begin(), value.end()),
|
||||
time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a boolean array entry value.
|
||||
*
|
||||
@@ -385,6 +400,20 @@ class Value final {
|
||||
static std::shared_ptr<Value> MakeBooleanArray(ArrayRef<int> value,
|
||||
uint64_t time = 0);
|
||||
|
||||
/**
|
||||
* Creates a boolean array entry value.
|
||||
*
|
||||
* @param value the value
|
||||
* @param time if nonzero, the creation time to use (instead of the current
|
||||
* time)
|
||||
* @return The entry value
|
||||
*/
|
||||
static std::shared_ptr<Value> MakeBooleanArray(
|
||||
std::initializer_list<int> value, uint64_t time = 0) {
|
||||
return MakeBooleanArray(wpi::makeArrayRef(value.begin(), value.end()),
|
||||
time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a double array entry value.
|
||||
*
|
||||
@@ -396,6 +425,19 @@ class Value final {
|
||||
static std::shared_ptr<Value> MakeDoubleArray(ArrayRef<double> value,
|
||||
uint64_t time = 0);
|
||||
|
||||
/**
|
||||
* Creates a double array entry value.
|
||||
*
|
||||
* @param value the value
|
||||
* @param time if nonzero, the creation time to use (instead of the current
|
||||
* time)
|
||||
* @return The entry value
|
||||
*/
|
||||
static std::shared_ptr<Value> MakeDoubleArray(
|
||||
std::initializer_list<double> value, uint64_t time = 0) {
|
||||
return MakeDoubleArray(wpi::makeArrayRef(value.begin(), value.end()), time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string array entry value.
|
||||
*
|
||||
@@ -407,6 +449,19 @@ class Value final {
|
||||
static std::shared_ptr<Value> MakeStringArray(ArrayRef<std::string> value,
|
||||
uint64_t time = 0);
|
||||
|
||||
/**
|
||||
* Creates a string array entry value.
|
||||
*
|
||||
* @param value the value
|
||||
* @param time if nonzero, the creation time to use (instead of the current
|
||||
* time)
|
||||
* @return The entry value
|
||||
*/
|
||||
static std::shared_ptr<Value> MakeStringArray(
|
||||
std::initializer_list<std::string> value, uint64_t time = 0) {
|
||||
return MakeStringArray(wpi::makeArrayRef(value.begin(), value.end()), time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string array entry value.
|
||||
*
|
||||
|
||||
@@ -26,8 +26,7 @@ LinearFilter LinearFilter::SinglePoleIIR(double timeConstant, double period) {
|
||||
|
||||
LinearFilter LinearFilter::HighPass(double timeConstant, double period) {
|
||||
double gain = std::exp(-period / timeConstant);
|
||||
const double ffGains[] = {gain, -gain};
|
||||
return LinearFilter(ffGains, -gain);
|
||||
return LinearFilter({gain, -gain}, {-gain});
|
||||
}
|
||||
|
||||
LinearFilter LinearFilter::MovingAverage(int taps) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
|
||||
/* Copyright (c) 2015-2019 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
@@ -27,6 +27,13 @@ LinearDigitalFilter::LinearDigitalFilter(PIDSource& source,
|
||||
HAL_Report(HALUsageReporting::kResourceType_LinearFilter, instances);
|
||||
}
|
||||
|
||||
LinearDigitalFilter::LinearDigitalFilter(PIDSource& source,
|
||||
std::initializer_list<double> ffGains,
|
||||
std::initializer_list<double> fbGains)
|
||||
: LinearDigitalFilter(source,
|
||||
wpi::makeArrayRef(ffGains.begin(), ffGains.end()),
|
||||
wpi::makeArrayRef(fbGains.begin(), fbGains.end())) {}
|
||||
|
||||
LinearDigitalFilter::LinearDigitalFilter(std::shared_ptr<PIDSource> source,
|
||||
wpi::ArrayRef<double> ffGains,
|
||||
wpi::ArrayRef<double> fbGains)
|
||||
@@ -40,6 +47,13 @@ LinearDigitalFilter::LinearDigitalFilter(std::shared_ptr<PIDSource> source,
|
||||
HAL_Report(HALUsageReporting::kResourceType_LinearFilter, instances);
|
||||
}
|
||||
|
||||
LinearDigitalFilter::LinearDigitalFilter(std::shared_ptr<PIDSource> source,
|
||||
std::initializer_list<double> ffGains,
|
||||
std::initializer_list<double> fbGains)
|
||||
: LinearDigitalFilter(source,
|
||||
wpi::makeArrayRef(ffGains.begin(), ffGains.end()),
|
||||
wpi::makeArrayRef(fbGains.begin(), fbGains.end())) {}
|
||||
|
||||
LinearDigitalFilter LinearDigitalFilter::SinglePoleIIR(PIDSource& source,
|
||||
double timeConstant,
|
||||
double period) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Copyright (c) 2018-2019 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
@@ -44,7 +44,6 @@ void RecordingController::AddEventMarker(
|
||||
DriverStation::ReportError("Shuffleboard event name was not specified");
|
||||
return;
|
||||
}
|
||||
auto arr = wpi::ArrayRef<std::string>{
|
||||
description, ShuffleboardEventImportanceName(importance)};
|
||||
m_eventsTable->GetSubTable(name)->GetEntry("Info").SetStringArray(arr);
|
||||
m_eventsTable->GetSubTable(name)->GetEntry("Info").SetStringArray(
|
||||
{description, ShuffleboardEventImportanceName(importance)});
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <initializer_list>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/ArrayRef.h>
|
||||
@@ -75,6 +76,17 @@ class LinearFilter {
|
||||
*/
|
||||
LinearFilter(wpi::ArrayRef<double> ffGains, wpi::ArrayRef<double> fbGains);
|
||||
|
||||
/**
|
||||
* Create a linear FIR or IIR filter.
|
||||
*
|
||||
* @param ffGains The "feed forward" or FIR gains.
|
||||
* @param fbGains The "feed back" or IIR gains.
|
||||
*/
|
||||
LinearFilter(std::initializer_list<double> ffGains,
|
||||
std::initializer_list<double> fbGains)
|
||||
: LinearFilter(wpi::makeArrayRef(ffGains.begin(), ffGains.end()),
|
||||
wpi::makeArrayRef(fbGains.begin(), fbGains.end())) {}
|
||||
|
||||
LinearFilter(LinearFilter&&) = default;
|
||||
LinearFilter& operator=(LinearFilter&&) = default;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
@@ -81,6 +82,17 @@ class LinearDigitalFilter : public Filter {
|
||||
LinearDigitalFilter(PIDSource& source, wpi::ArrayRef<double> ffGains,
|
||||
wpi::ArrayRef<double> fbGains);
|
||||
|
||||
/**
|
||||
* Create a linear FIR or IIR filter.
|
||||
*
|
||||
* @param source The PIDSource object that is used to get values
|
||||
* @param ffGains The "feed forward" or FIR gains
|
||||
* @param fbGains The "feed back" or IIR gains
|
||||
*/
|
||||
WPI_DEPRECATED("Use LinearFilter class instead.")
|
||||
LinearDigitalFilter(PIDSource& source, std::initializer_list<double> ffGains,
|
||||
std::initializer_list<double> fbGains);
|
||||
|
||||
/**
|
||||
* Create a linear FIR or IIR filter.
|
||||
*
|
||||
@@ -93,6 +105,18 @@ class LinearDigitalFilter : public Filter {
|
||||
wpi::ArrayRef<double> ffGains,
|
||||
wpi::ArrayRef<double> fbGains);
|
||||
|
||||
/**
|
||||
* Create a linear FIR or IIR filter.
|
||||
*
|
||||
* @param source The PIDSource object that is used to get values
|
||||
* @param ffGains The "feed forward" or FIR gains
|
||||
* @param fbGains The "feed back" or IIR gains
|
||||
*/
|
||||
WPI_DEPRECATED("Use LinearFilter class instead.")
|
||||
LinearDigitalFilter(std::shared_ptr<PIDSource> source,
|
||||
std::initializer_list<double> ffGains,
|
||||
std::initializer_list<double> fbGains);
|
||||
|
||||
LinearDigitalFilter(LinearDigitalFilter&&) = default;
|
||||
LinearDigitalFilter& operator=(LinearDigitalFilter&&) = default;
|
||||
|
||||
|
||||
@@ -286,8 +286,9 @@ void WebSocket::SendClose(uint16_t code, const Twine& reason) {
|
||||
SmallVector<uv::Buffer, 4> bufs;
|
||||
if (code != 1005) {
|
||||
raw_uv_ostream os{bufs, 4096};
|
||||
os << ArrayRef<uint8_t>{static_cast<uint8_t>((code >> 8) & 0xff),
|
||||
static_cast<uint8_t>(code & 0xff)};
|
||||
const uint8_t codeMsb[] = {static_cast<uint8_t>((code >> 8) & 0xff),
|
||||
static_cast<uint8_t>(code & 0xff)};
|
||||
os << ArrayRef<uint8_t>(codeMsb);
|
||||
reason.print(os);
|
||||
}
|
||||
Send(kFlagFin | kOpClose, bufs, [](auto bufs, uv::Error) {
|
||||
@@ -520,18 +521,20 @@ void WebSocket::Send(
|
||||
os << static_cast<unsigned char>((m_server ? 0x00 : kFlagMasking) | size);
|
||||
} else if (size <= 0xffff) {
|
||||
os << static_cast<unsigned char>((m_server ? 0x00 : kFlagMasking) | 126);
|
||||
os << ArrayRef<uint8_t>{static_cast<uint8_t>((size >> 8) & 0xff),
|
||||
static_cast<uint8_t>(size & 0xff)};
|
||||
const uint8_t sizeMsb[] = {static_cast<uint8_t>((size >> 8) & 0xff),
|
||||
static_cast<uint8_t>(size & 0xff)};
|
||||
os << ArrayRef<uint8_t>(sizeMsb);
|
||||
} else {
|
||||
os << static_cast<unsigned char>((m_server ? 0x00 : kFlagMasking) | 127);
|
||||
os << ArrayRef<uint8_t>{static_cast<uint8_t>((size >> 56) & 0xff),
|
||||
static_cast<uint8_t>((size >> 48) & 0xff),
|
||||
static_cast<uint8_t>((size >> 40) & 0xff),
|
||||
static_cast<uint8_t>((size >> 32) & 0xff),
|
||||
static_cast<uint8_t>((size >> 24) & 0xff),
|
||||
static_cast<uint8_t>((size >> 16) & 0xff),
|
||||
static_cast<uint8_t>((size >> 8) & 0xff),
|
||||
static_cast<uint8_t>(size & 0xff)};
|
||||
const uint8_t sizeMsb[] = {static_cast<uint8_t>((size >> 56) & 0xff),
|
||||
static_cast<uint8_t>((size >> 48) & 0xff),
|
||||
static_cast<uint8_t>((size >> 40) & 0xff),
|
||||
static_cast<uint8_t>((size >> 32) & 0xff),
|
||||
static_cast<uint8_t>((size >> 24) & 0xff),
|
||||
static_cast<uint8_t>((size >> 16) & 0xff),
|
||||
static_cast<uint8_t>((size >> 8) & 0xff),
|
||||
static_cast<uint8_t>(size & 0xff)};
|
||||
os << ArrayRef<uint8_t>(sizeMsb);
|
||||
}
|
||||
|
||||
// clients need to mask the input data
|
||||
|
||||
@@ -98,11 +98,6 @@ namespace wpi {
|
||||
template <size_t N>
|
||||
/*implicit*/ constexpr ArrayRef(const T (&Arr)[N]) : Data(Arr), Length(N) {}
|
||||
|
||||
/// Construct an ArrayRef from a std::initializer_list.
|
||||
/*implicit*/ ArrayRef(const std::initializer_list<T> &Vec)
|
||||
: Data(Vec.begin() == Vec.end() ? (T*)nullptr : Vec.begin()),
|
||||
Length(Vec.size()) {}
|
||||
|
||||
/// Construct an ArrayRef<const T*> from ArrayRef<T*>. This uses SFINAE to
|
||||
/// ensure that only ArrayRefs of pointers can be converted.
|
||||
template <typename U>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -99,6 +100,25 @@ class WebSocket : public std::enable_shared_from_this<WebSocket> {
|
||||
ArrayRef<StringRef> protocols = ArrayRef<StringRef>{},
|
||||
const ClientOptions& options = ClientOptions{});
|
||||
|
||||
/**
|
||||
* Starts a client connection by performing the initial client handshake.
|
||||
* An open event is emitted when the handshake completes.
|
||||
* This sets the stream user data to the websocket.
|
||||
* @param stream Connection stream
|
||||
* @param uri The Request-URI to send
|
||||
* @param host The host or host:port to send
|
||||
* @param protocols The list of subprotocols
|
||||
* @param options Handshake options
|
||||
*/
|
||||
static std::shared_ptr<WebSocket> CreateClient(
|
||||
uv::Stream& stream, const Twine& uri, const Twine& host,
|
||||
std::initializer_list<StringRef> protocols,
|
||||
const ClientOptions& options = ClientOptions{}) {
|
||||
return CreateClient(stream, uri, host,
|
||||
makeArrayRef(protocols.begin(), protocols.end()),
|
||||
options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a server connection by performing the initial server side handshake.
|
||||
* This should be called after the HTTP headers have been received.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Copyright (c) 2018-2019 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
@@ -9,6 +9,7 @@
|
||||
#define WPIUTIL_WPI_WEBSOCKETSERVER_H_
|
||||
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -56,6 +57,20 @@ class WebSocketServerHelper {
|
||||
*/
|
||||
std::pair<bool, StringRef> MatchProtocol(ArrayRef<StringRef> protocols);
|
||||
|
||||
/**
|
||||
* Try to find a match to the list of sub-protocols provided by the client.
|
||||
* The list is priority ordered, so the first match wins.
|
||||
* Only valid during and after the upgrade event.
|
||||
* @param protocols Acceptable protocols
|
||||
* @return Pair; first item is true if a match was made, false if not.
|
||||
* Second item is the matched protocol if a match was made, otherwise
|
||||
* is empty.
|
||||
*/
|
||||
std::pair<bool, StringRef> MatchProtocol(
|
||||
std::initializer_list<StringRef> protocols) {
|
||||
return MatchProtocol(makeArrayRef(protocols.begin(), protocols.end()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept the upgrade. Disconnect other readers (such as the HttpParser
|
||||
* reader) before calling this. See also WebSocket::CreateServer().
|
||||
@@ -125,6 +140,22 @@ class WebSocketServer : public std::enable_shared_from_this<WebSocketServer> {
|
||||
uv::Stream& stream, ArrayRef<StringRef> protocols = ArrayRef<StringRef>{},
|
||||
const ServerOptions& options = ServerOptions{});
|
||||
|
||||
/**
|
||||
* Starts a dedicated WebSocket server on the provided connection. The
|
||||
* connection should be an accepted client stream.
|
||||
* This also sets the stream user data to the socket server.
|
||||
* A connected event is emitted when the connection is opened.
|
||||
* @param stream Connection stream
|
||||
* @param protocols Acceptable subprotocols
|
||||
* @param options Handshake options
|
||||
*/
|
||||
static std::shared_ptr<WebSocketServer> Create(
|
||||
uv::Stream& stream, std::initializer_list<StringRef> protocols,
|
||||
const ServerOptions& options = ServerOptions{}) {
|
||||
return Create(stream, makeArrayRef(protocols.begin(), protocols.end()),
|
||||
options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connected event. First parameter is the URL, second is the websocket.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Copyright (c) 2018-2019 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <uv.h>
|
||||
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
|
||||
#include "wpi/ArrayRef.h"
|
||||
@@ -130,6 +131,27 @@ class Stream : public Handle {
|
||||
*/
|
||||
void Write(ArrayRef<Buffer> bufs, const std::shared_ptr<WriteReq>& req);
|
||||
|
||||
/**
|
||||
* Write data to the stream.
|
||||
*
|
||||
* Data are written in order. The lifetime of the data pointers passed in
|
||||
* the `bufs` parameter must exceed the lifetime of the write request.
|
||||
* An easy way to ensure this is to have the write request keep track of
|
||||
* the data and use either its Complete() function or destructor to free the
|
||||
* data.
|
||||
*
|
||||
* The finish signal will be emitted on the request object when the data
|
||||
* has been written (or if an error occurs).
|
||||
* The error signal will be emitted on the request object in case of errors.
|
||||
*
|
||||
* @param bufs The buffers to be written to the stream.
|
||||
* @param req write request
|
||||
*/
|
||||
void Write(std::initializer_list<Buffer> bufs,
|
||||
const std::shared_ptr<WriteReq>& req) {
|
||||
Write(makeArrayRef(bufs.begin(), bufs.end()), req);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write data to the stream.
|
||||
*
|
||||
@@ -146,6 +168,24 @@ class Stream : public Handle {
|
||||
void Write(ArrayRef<Buffer> bufs,
|
||||
std::function<void(MutableArrayRef<Buffer>, Error)> callback);
|
||||
|
||||
/**
|
||||
* Write data to the stream.
|
||||
*
|
||||
* Data are written in order. The lifetime of the data pointers passed in
|
||||
* the `bufs` parameter must exceed the lifetime of the write request.
|
||||
* The callback can be used to free data after the request completes.
|
||||
*
|
||||
* The callback will be called when the data has been written (even if an
|
||||
* error occurred). Errors will be reported to the stream error handler.
|
||||
*
|
||||
* @param bufs The buffers to be written to the stream.
|
||||
* @param callback Callback function to call when the write completes
|
||||
*/
|
||||
void Write(std::initializer_list<Buffer> bufs,
|
||||
std::function<void(MutableArrayRef<Buffer>, Error)> callback) {
|
||||
Write(makeArrayRef(bufs.begin(), bufs.end()), callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a write request if it can be completed immediately.
|
||||
*
|
||||
@@ -158,6 +198,20 @@ class Stream : public Handle {
|
||||
*/
|
||||
int TryWrite(ArrayRef<Buffer> bufs);
|
||||
|
||||
/**
|
||||
* Queue a write request if it can be completed immediately.
|
||||
*
|
||||
* Same as `Write()`, but won’t queue a write request if it can’t be
|
||||
* completed immediately.
|
||||
* An error signal will be emitted in case of errors.
|
||||
*
|
||||
* @param bufs The buffers to be written to the stream.
|
||||
* @return Number of bytes written.
|
||||
*/
|
||||
int TryWrite(std::initializer_list<Buffer> bufs) {
|
||||
return TryWrite(makeArrayRef(bufs.begin(), bufs.end()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the stream is readable.
|
||||
* @return True if the stream is readable, false otherwise.
|
||||
|
||||
@@ -48,14 +48,16 @@ static bool NewlineBuffer(std::string& rem, uv::Buffer& buf, size_t len,
|
||||
// Header is 2 byte len, 1 byte type, 4 byte timestamp, 2 byte sequence num
|
||||
uint32_t ts = wpi::FloatToBits((wpi::Now() - startTime) * 1.0e-6);
|
||||
uint16_t len = rem.size() + toCopy.size() + 1 + 4 + 2;
|
||||
out << wpi::ArrayRef<uint8_t>({static_cast<uint8_t>((len >> 8) & 0xff),
|
||||
static_cast<uint8_t>(len & 0xff), 12,
|
||||
static_cast<uint8_t>((ts >> 24) & 0xff),
|
||||
static_cast<uint8_t>((ts >> 16) & 0xff),
|
||||
static_cast<uint8_t>((ts >> 8) & 0xff),
|
||||
static_cast<uint8_t>(ts & 0xff),
|
||||
static_cast<uint8_t>((tcpSeq >> 8) & 0xff),
|
||||
static_cast<uint8_t>(tcpSeq & 0xff)});
|
||||
const uint8_t header[] = {static_cast<uint8_t>((len >> 8) & 0xff),
|
||||
static_cast<uint8_t>(len & 0xff),
|
||||
12,
|
||||
static_cast<uint8_t>((ts >> 24) & 0xff),
|
||||
static_cast<uint8_t>((ts >> 16) & 0xff),
|
||||
static_cast<uint8_t>((ts >> 8) & 0xff),
|
||||
static_cast<uint8_t>(ts & 0xff),
|
||||
static_cast<uint8_t>((tcpSeq >> 8) & 0xff),
|
||||
static_cast<uint8_t>(tcpSeq & 0xff)};
|
||||
out << wpi::ArrayRef<uint8_t>(header);
|
||||
}
|
||||
out << rem << toCopy;
|
||||
|
||||
|
||||
@@ -39,14 +39,16 @@ static bool NewlineBuffer(std::string& rem, uv::Buffer& buf, size_t len,
|
||||
// Header is 2 byte len, 1 byte type, 4 byte timestamp, 2 byte sequence num
|
||||
uint32_t ts = wpi::FloatToBits((wpi::Now() - startTime) * 1.0e-6);
|
||||
uint16_t len = rem.size() + toCopy.size() + 1 + 4 + 2;
|
||||
out << wpi::ArrayRef<uint8_t>({static_cast<uint8_t>((len >> 8) & 0xff),
|
||||
static_cast<uint8_t>(len & 0xff), 12,
|
||||
static_cast<uint8_t>((ts >> 24) & 0xff),
|
||||
static_cast<uint8_t>((ts >> 16) & 0xff),
|
||||
static_cast<uint8_t>((ts >> 8) & 0xff),
|
||||
static_cast<uint8_t>(ts & 0xff),
|
||||
static_cast<uint8_t>((tcpSeq >> 8) & 0xff),
|
||||
static_cast<uint8_t>(tcpSeq & 0xff)});
|
||||
const uint8_t header[] = {static_cast<uint8_t>((len >> 8) & 0xff),
|
||||
static_cast<uint8_t>(len & 0xff),
|
||||
12,
|
||||
static_cast<uint8_t>((ts >> 24) & 0xff),
|
||||
static_cast<uint8_t>((ts >> 16) & 0xff),
|
||||
static_cast<uint8_t>((ts >> 8) & 0xff),
|
||||
static_cast<uint8_t>(ts & 0xff),
|
||||
static_cast<uint8_t>((tcpSeq >> 8) & 0xff),
|
||||
static_cast<uint8_t>(tcpSeq & 0xff)};
|
||||
out << wpi::ArrayRef<uint8_t>(header);
|
||||
}
|
||||
out << rem << toCopy;
|
||||
|
||||
|
||||
@@ -135,9 +135,8 @@ TEST_F(WebSocketClientTest, ProtocolGood) {
|
||||
mockProtocol = "myProtocol";
|
||||
|
||||
clientPipe->Connect(pipeName, [&] {
|
||||
auto ws = WebSocket::CreateClient(
|
||||
*clientPipe, "/test", pipeName,
|
||||
ArrayRef<StringRef>{"myProtocol", "myProtocol2"});
|
||||
auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName,
|
||||
{"myProtocol", "myProtocol2"});
|
||||
ws->closed.connect([&](uint16_t code, StringRef msg) {
|
||||
Finish();
|
||||
if (code != 1005 && code != 1006)
|
||||
|
||||
@@ -141,14 +141,15 @@ TEST_F(WebSocketServerTest, CloseCode) {
|
||||
});
|
||||
};
|
||||
// need to respond with close for server to finish shutdown
|
||||
auto message = BuildMessage(0x08, true, true, {0x03u, 0xe8u});
|
||||
const uint8_t contents[] = {0x03u, 0xe8u};
|
||||
auto message = BuildMessage(0x08, true, true, contents);
|
||||
handleData = [&](StringRef) {
|
||||
clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
|
||||
};
|
||||
|
||||
loop->Run();
|
||||
|
||||
auto expectData = BuildMessage(0x08, true, false, {0x03u, 0xe8u});
|
||||
auto expectData = BuildMessage(0x08, true, false, contents);
|
||||
ASSERT_EQ(wireData, expectData);
|
||||
ASSERT_EQ(gotClosed, 1);
|
||||
}
|
||||
@@ -164,16 +165,15 @@ TEST_F(WebSocketServerTest, CloseReason) {
|
||||
});
|
||||
};
|
||||
// need to respond with close for server to finish shutdown
|
||||
auto message = BuildMessage(0x08, true, true,
|
||||
{0x03u, 0xe8u, 'h', 'a', 'n', 'g', 'u', 'p'});
|
||||
const uint8_t contents[] = {0x03u, 0xe8u, 'h', 'a', 'n', 'g', 'u', 'p'};
|
||||
auto message = BuildMessage(0x08, true, true, contents);
|
||||
handleData = [&](StringRef) {
|
||||
clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
|
||||
};
|
||||
|
||||
loop->Run();
|
||||
|
||||
auto expectData = BuildMessage(0x08, true, false,
|
||||
{0x03u, 0xe8u, 'h', 'a', 'n', 'g', 'u', 'p'});
|
||||
auto expectData = BuildMessage(0x08, true, false, contents);
|
||||
ASSERT_EQ(wireData, expectData);
|
||||
ASSERT_EQ(gotClosed, 1);
|
||||
}
|
||||
@@ -211,7 +211,8 @@ TEST_F(WebSocketServerTest, ReceiveCloseCode) {
|
||||
ASSERT_EQ(code, 1000) << "reason: " << reason;
|
||||
});
|
||||
};
|
||||
auto message = BuildMessage(0x08, true, true, {0x03u, 0xe8u});
|
||||
const uint8_t contents[] = {0x03u, 0xe8u};
|
||||
auto message = BuildMessage(0x08, true, true, contents);
|
||||
resp.headersComplete.connect([&](bool) {
|
||||
clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
|
||||
});
|
||||
@@ -219,7 +220,7 @@ TEST_F(WebSocketServerTest, ReceiveCloseCode) {
|
||||
loop->Run();
|
||||
|
||||
// the endpoint should echo the message
|
||||
auto expectData = BuildMessage(0x08, true, false, {0x03u, 0xe8u});
|
||||
auto expectData = BuildMessage(0x08, true, false, contents);
|
||||
ASSERT_EQ(wireData, expectData);
|
||||
ASSERT_EQ(gotClosed, 1);
|
||||
}
|
||||
@@ -233,8 +234,8 @@ TEST_F(WebSocketServerTest, ReceiveCloseReason) {
|
||||
ASSERT_EQ(reason, "hangup");
|
||||
});
|
||||
};
|
||||
auto message = BuildMessage(0x08, true, true,
|
||||
{0x03u, 0xe8u, 'h', 'a', 'n', 'g', 'u', 'p'});
|
||||
const uint8_t contents[] = {0x03u, 0xe8u, 'h', 'a', 'n', 'g', 'u', 'p'};
|
||||
auto message = BuildMessage(0x08, true, true, contents);
|
||||
resp.headersComplete.connect([&](bool) {
|
||||
clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
|
||||
});
|
||||
@@ -242,8 +243,7 @@ TEST_F(WebSocketServerTest, ReceiveCloseReason) {
|
||||
loop->Run();
|
||||
|
||||
// the endpoint should echo the message
|
||||
auto expectData = BuildMessage(0x08, true, false,
|
||||
{0x03u, 0xe8u, 'h', 'a', 'n', 'g', 'u', 'p'});
|
||||
auto expectData = BuildMessage(0x08, true, false, contents);
|
||||
ASSERT_EQ(wireData, expectData);
|
||||
ASSERT_EQ(gotClosed, 1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user