Vendor dependencies
Let's see how I like this workflow.
This commit is contained in:
parent
34d1830413
commit
9c435dc440
7500 changed files with 1665121 additions and 99 deletions
49
vendor/iana-time-zone/src/ffi_utils.rs
vendored
Normal file
49
vendor/iana-time-zone/src/ffi_utils.rs
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
//! Cross platform FFI helpers.
|
||||
|
||||
use std::ffi::CStr;
|
||||
|
||||
// The system property named 'persist.sys.timezone' contains the name of the
|
||||
// current timezone.
|
||||
//
|
||||
// From https://android.googlesource.com/platform/bionic/+/gingerbread-release/libc/docs/OVERVIEW.TXT#79:
|
||||
//
|
||||
// > The name of the current timezone is taken from the TZ environment variable,
|
||||
// > if defined. Otherwise, the system property named 'persist.sys.timezone' is
|
||||
// > checked instead.
|
||||
const ANDROID_TIMEZONE_PROPERTY_NAME: &[u8] = b"persist.sys.timezone\0";
|
||||
|
||||
/// Return a [`CStr`] to access the timezone from an Android system properties
|
||||
/// environment.
|
||||
pub(crate) fn android_timezone_property_name() -> &'static CStr {
|
||||
// In tests or debug mode, opt into extra runtime checks.
|
||||
if cfg!(any(test, debug_assertions)) {
|
||||
return CStr::from_bytes_with_nul(ANDROID_TIMEZONE_PROPERTY_NAME).unwrap();
|
||||
}
|
||||
|
||||
// SAFETY: the key is NUL-terminated and there are no other NULs, this
|
||||
// invariant is checked in tests.
|
||||
unsafe { CStr::from_bytes_with_nul_unchecked(ANDROID_TIMEZONE_PROPERTY_NAME) }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ffi::CStr;
|
||||
|
||||
use super::{android_timezone_property_name, ANDROID_TIMEZONE_PROPERTY_NAME};
|
||||
|
||||
#[test]
|
||||
fn test_android_timezone_property_name_is_valid_cstr() {
|
||||
CStr::from_bytes_with_nul(ANDROID_TIMEZONE_PROPERTY_NAME).unwrap();
|
||||
|
||||
let mut invalid_property_name = ANDROID_TIMEZONE_PROPERTY_NAME.to_owned();
|
||||
invalid_property_name.push(b'\0');
|
||||
CStr::from_bytes_with_nul(&invalid_property_name).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_android_timezone_property_name_getter() {
|
||||
let key = android_timezone_property_name().to_bytes_with_nul();
|
||||
assert_eq!(key, ANDROID_TIMEZONE_PROPERTY_NAME);
|
||||
std::str::from_utf8(key).unwrap();
|
||||
}
|
||||
}
|
||||
113
vendor/iana-time-zone/src/lib.rs
vendored
Normal file
113
vendor/iana-time-zone/src/lib.rs
vendored
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
#![warn(clippy::all)]
|
||||
#![warn(clippy::cargo)]
|
||||
#![warn(clippy::undocumented_unsafe_blocks)]
|
||||
#![allow(unknown_lints)]
|
||||
#![warn(missing_copy_implementations)]
|
||||
#![warn(missing_debug_implementations)]
|
||||
#![warn(missing_docs)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![warn(trivial_casts, trivial_numeric_casts)]
|
||||
#![warn(unused_qualifications)]
|
||||
#![warn(variant_size_differences)]
|
||||
|
||||
//! get the IANA time zone for the current system
|
||||
//!
|
||||
//! This small utility crate provides the
|
||||
//! [`get_timezone()`](fn.get_timezone.html) function.
|
||||
//!
|
||||
//! ```rust
|
||||
//! // Get the current time zone as a string.
|
||||
//! let tz_str = iana_time_zone::get_timezone()?;
|
||||
//! println!("The current time zone is: {}", tz_str);
|
||||
//! # Ok::<(), iana_time_zone::GetTimezoneError>(())
|
||||
//! ```
|
||||
//!
|
||||
//! The resulting string can be parsed to a
|
||||
//! [`chrono-tz::Tz`](https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)
|
||||
//! variant like this:
|
||||
//! ```ignore
|
||||
//! let tz_str = iana_time_zone::get_timezone()?;
|
||||
//! let tz: chrono_tz::Tz = tz_str.parse()?;
|
||||
//! ```
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod ffi_utils;
|
||||
|
||||
#[cfg_attr(target_os = "linux", path = "tz_linux.rs")]
|
||||
#[cfg_attr(target_os = "windows", path = "tz_windows.rs")]
|
||||
#[cfg_attr(any(target_os = "macos", target_os = "ios"), path = "tz_macos.rs")]
|
||||
#[cfg_attr(
|
||||
all(target_arch = "wasm32", not(target_os = "wasi")),
|
||||
path = "tz_wasm32.rs"
|
||||
)]
|
||||
#[cfg_attr(
|
||||
any(target_os = "freebsd", target_os = "dragonfly"),
|
||||
path = "tz_freebsd.rs"
|
||||
)]
|
||||
#[cfg_attr(
|
||||
any(target_os = "netbsd", target_os = "openbsd"),
|
||||
path = "tz_netbsd.rs"
|
||||
)]
|
||||
#[cfg_attr(
|
||||
any(target_os = "illumos", target_os = "solaris"),
|
||||
path = "tz_illumos.rs"
|
||||
)]
|
||||
#[cfg_attr(target_os = "android", path = "tz_android.rs")]
|
||||
#[cfg_attr(target_os = "haiku", path = "tz_haiku.rs")]
|
||||
mod platform;
|
||||
|
||||
/// Error types
|
||||
#[derive(Debug)]
|
||||
pub enum GetTimezoneError {
|
||||
/// Failed to parse
|
||||
FailedParsingString,
|
||||
/// Wrapped IO error
|
||||
IoError(std::io::Error),
|
||||
/// Platform-specific error from the operating system
|
||||
OsError,
|
||||
}
|
||||
|
||||
impl std::error::Error for GetTimezoneError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
GetTimezoneError::FailedParsingString => None,
|
||||
GetTimezoneError::IoError(err) => Some(err),
|
||||
GetTimezoneError::OsError => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for GetTimezoneError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
f.write_str(match self {
|
||||
GetTimezoneError::FailedParsingString => "GetTimezoneError::FailedParsingString",
|
||||
GetTimezoneError::IoError(err) => return err.fmt(f),
|
||||
GetTimezoneError::OsError => "OsError",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for GetTimezoneError {
|
||||
fn from(orig: std::io::Error) -> Self {
|
||||
GetTimezoneError::IoError(orig)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current IANA time zone as a string.
|
||||
///
|
||||
/// See the module-level documentatation for a usage example and more details
|
||||
/// about this function.
|
||||
#[inline]
|
||||
pub fn get_timezone() -> Result<String, GetTimezoneError> {
|
||||
platform::get_timezone_inner()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn get_current() {
|
||||
println!("current: {}", get_timezone().unwrap());
|
||||
}
|
||||
}
|
||||
9
vendor/iana-time-zone/src/platform.rs
vendored
Normal file
9
vendor/iana-time-zone/src/platform.rs
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
pub fn get_timezone_inner() -> std::result::Result<String, crate::GetTimezoneError> {
|
||||
Err(crate::GetTimezoneError::OsError)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "fallback"))]
|
||||
compile_error!(
|
||||
"iana-time-zone is currently implemented for Linux, Window, MacOS, FreeBSD, NetBSD, \
|
||||
OpenBSD, Dragonfly, WebAssembly (browser), iOS, Illumos, Android, Solaris and Haiku.",
|
||||
);
|
||||
27
vendor/iana-time-zone/src/tz_android.rs
vendored
Normal file
27
vendor/iana-time-zone/src/tz_android.rs
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use std::sync::Once;
|
||||
|
||||
use android_system_properties::AndroidSystemProperties;
|
||||
|
||||
use crate::ffi_utils::android_timezone_property_name;
|
||||
|
||||
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
|
||||
let key = android_timezone_property_name();
|
||||
|
||||
get_properties()
|
||||
.and_then(|properties| properties.get_from_cstr(key))
|
||||
.ok_or(crate::GetTimezoneError::OsError)
|
||||
}
|
||||
|
||||
fn get_properties() -> Option<&'static AndroidSystemProperties> {
|
||||
static INITIALIZED: Once = Once::new();
|
||||
static mut PROPERTIES: Option<AndroidSystemProperties> = None;
|
||||
|
||||
INITIALIZED.call_once(|| {
|
||||
let properties = AndroidSystemProperties::new();
|
||||
// SAFETY: `INITIALIZED` is synchronizing. The variable is only assigned to once.
|
||||
unsafe { PROPERTIES = Some(properties) };
|
||||
});
|
||||
|
||||
// SAFETY: `INITIALIZED` is synchronizing. The variable is only assigned to once.
|
||||
unsafe { PROPERTIES.as_ref() }
|
||||
}
|
||||
7
vendor/iana-time-zone/src/tz_freebsd.rs
vendored
Normal file
7
vendor/iana-time-zone/src/tz_freebsd.rs
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
|
||||
// see https://gitlab.gnome.org/GNOME/evolution-data-server/-/issues/19
|
||||
let mut contents = std::fs::read_to_string("/var/db/zoneinfo")?;
|
||||
// Trim to the correct length without allocating.
|
||||
contents.truncate(contents.trim_end().len());
|
||||
Ok(contents)
|
||||
}
|
||||
3
vendor/iana-time-zone/src/tz_haiku.rs
vendored
Normal file
3
vendor/iana-time-zone/src/tz_haiku.rs
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
|
||||
iana_time_zone_haiku::get_timezone().ok_or(crate::GetTimezoneError::OsError)
|
||||
}
|
||||
22
vendor/iana-time-zone/src/tz_illumos.rs
vendored
Normal file
22
vendor/iana-time-zone/src/tz_illumos.rs
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use std::fs::OpenOptions;
|
||||
use std::io::{BufRead, BufReader};
|
||||
|
||||
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
|
||||
// https://illumos.org/man/5/TIMEZONE
|
||||
// https://docs.oracle.com/cd/E23824_01/html/821-1473/uc-timezone-4.html
|
||||
|
||||
let file = OpenOptions::new().read(true).open("/etc/default/init")?;
|
||||
let mut reader = BufReader::with_capacity(1536, file);
|
||||
let mut line = String::with_capacity(80);
|
||||
loop {
|
||||
line.clear();
|
||||
let count = reader.read_line(&mut line)?;
|
||||
if count == 0 {
|
||||
return Err(crate::GetTimezoneError::FailedParsingString);
|
||||
} else if line.starts_with("TZ=") {
|
||||
line.truncate(line.trim_end().len());
|
||||
line.replace_range(..3, "");
|
||||
return Ok(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
45
vendor/iana-time-zone/src/tz_linux.rs
vendored
Normal file
45
vendor/iana-time-zone/src/tz_linux.rs
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use std::fs::{read_link, read_to_string};
|
||||
|
||||
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
|
||||
etc_localtime().or_else(|_| etc_timezone())
|
||||
}
|
||||
|
||||
fn etc_timezone() -> Result<String, crate::GetTimezoneError> {
|
||||
// see https://stackoverflow.com/a/12523283
|
||||
let mut contents = read_to_string("/etc/timezone")?;
|
||||
// Trim to the correct length without allocating.
|
||||
contents.truncate(contents.trim_end().len());
|
||||
Ok(contents)
|
||||
}
|
||||
|
||||
fn etc_localtime() -> Result<String, crate::GetTimezoneError> {
|
||||
// Per <https://www.man7.org/linux/man-pages/man5/localtime.5.html>:
|
||||
// “ The /etc/localtime file configures the system-wide timezone of the local system that is
|
||||
// used by applications for presentation to the user. It should be an absolute or relative
|
||||
// symbolic link pointing to /usr/share/zoneinfo/, followed by a timezone identifier such as
|
||||
// "Europe/Berlin" or "Etc/UTC". The resulting link should lead to the corresponding binary
|
||||
// tzfile(5) timezone data for the configured timezone. ”
|
||||
|
||||
// Systemd does not canonicalize the link, but only checks if it is prefixed by
|
||||
// "/usr/share/zoneinfo/" or "../usr/share/zoneinfo/". So we do the same.
|
||||
// <https://github.com/systemd/systemd/blob/9102c625a673a3246d7e73d8737f3494446bad4e/src/basic/time-util.c#L1493>
|
||||
|
||||
const PREFIXES: &[&str] = &[
|
||||
"/usr/share/zoneinfo/", // absolute path
|
||||
"../usr/share/zoneinfo/", // relative path
|
||||
"/etc/zoneinfo/", // absolute path for NixOS
|
||||
"../etc/zoneinfo/", // relative path for NixOS
|
||||
];
|
||||
let mut s = read_link("/etc/localtime")?
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.map_err(|_| crate::GetTimezoneError::FailedParsingString)?;
|
||||
for &prefix in PREFIXES {
|
||||
if s.starts_with(prefix) {
|
||||
// Trim to the correct length without allocating.
|
||||
s.replace_range(..prefix.len(), "");
|
||||
return Ok(s);
|
||||
}
|
||||
}
|
||||
Err(crate::GetTimezoneError::FailedParsingString)
|
||||
}
|
||||
144
vendor/iana-time-zone/src/tz_macos.rs
vendored
Normal file
144
vendor/iana-time-zone/src/tz_macos.rs
vendored
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
|
||||
get_timezone().ok_or(crate::GetTimezoneError::OsError)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_timezone() -> Option<String> {
|
||||
// The longest name in the IANA time zone database is 25 ASCII characters long.
|
||||
const MAX_LEN: usize = 32;
|
||||
let mut buf = [0; MAX_LEN];
|
||||
|
||||
// Get system time zone, and borrow its name.
|
||||
let tz = system_time_zone::SystemTimeZone::new()?;
|
||||
let name = tz.name()?;
|
||||
|
||||
// If the name is encoded in UTF-8, copy it directly.
|
||||
let name = if let Some(name) = name.as_utf8() {
|
||||
name
|
||||
} else {
|
||||
// Otherwise convert the name to UTF-8.
|
||||
name.to_utf8(&mut buf)?
|
||||
};
|
||||
|
||||
if name.is_empty() || name.len() >= MAX_LEN {
|
||||
// The name should not be empty, or excessively long.
|
||||
None
|
||||
} else {
|
||||
Some(name.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
mod system_time_zone {
|
||||
//! create a safe wrapper around `CFTimeZoneRef`
|
||||
|
||||
use core_foundation_sys::base::{CFRelease, CFTypeRef};
|
||||
use core_foundation_sys::timezone::{CFTimeZoneCopySystem, CFTimeZoneGetName, CFTimeZoneRef};
|
||||
|
||||
pub(crate) struct SystemTimeZone(CFTimeZoneRef);
|
||||
|
||||
impl Drop for SystemTimeZone {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: `SystemTimeZone` is only ever created with a valid `CFTimeZoneRef`.
|
||||
unsafe { CFRelease(self.0 as CFTypeRef) };
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemTimeZone {
|
||||
pub(crate) fn new() -> Option<Self> {
|
||||
// SAFETY: No invariants to uphold. We'll release the pointer when we don't need it anymore.
|
||||
let v: CFTimeZoneRef = unsafe { CFTimeZoneCopySystem() };
|
||||
if v.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(SystemTimeZone(v))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the time zone name as a [super::string_ref::StringRef].
|
||||
///
|
||||
/// The lifetime of the `StringRef` is bound to our lifetime. Mutable
|
||||
/// access is also prevented by taking a reference to `self`.
|
||||
pub(crate) fn name(&self) -> Option<super::string_ref::StringRef<'_, Self>> {
|
||||
// SAFETY: `SystemTimeZone` is only ever created with a valid `CFTimeZoneRef`.
|
||||
let string = unsafe { CFTimeZoneGetName(self.0) };
|
||||
if string.is_null() {
|
||||
None
|
||||
} else {
|
||||
// SAFETY: here we ensure that `string` is a valid pointer.
|
||||
Some(unsafe { super::string_ref::StringRef::new(string, self) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod string_ref {
|
||||
//! create safe wrapper around `CFStringRef`
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
use core_foundation_sys::base::{Boolean, CFRange};
|
||||
use core_foundation_sys::string::{
|
||||
kCFStringEncodingUTF8, CFStringGetBytes, CFStringGetCStringPtr, CFStringGetLength,
|
||||
CFStringRef,
|
||||
};
|
||||
|
||||
pub(crate) struct StringRef<'a, T> {
|
||||
string: CFStringRef,
|
||||
// We exclude mutable access to the parent by taking a reference to the
|
||||
// parent (rather than, for example, just using a marker to enforce the
|
||||
// parent's lifetime).
|
||||
_parent: &'a T,
|
||||
}
|
||||
|
||||
impl<'a, T> StringRef<'a, T> {
|
||||
// SAFETY: `StringRef` must be valid pointer
|
||||
pub(crate) unsafe fn new(string: CFStringRef, _parent: &'a T) -> Self {
|
||||
Self { string, _parent }
|
||||
}
|
||||
|
||||
pub(crate) fn as_utf8(&self) -> Option<&'a str> {
|
||||
// SAFETY: `StringRef` is only ever created with a valid `CFStringRef`.
|
||||
let v = unsafe { CFStringGetCStringPtr(self.string, kCFStringEncodingUTF8) };
|
||||
if !v.is_null() {
|
||||
// SAFETY: `CFStringGetCStringPtr()` returns NUL-terminated strings.
|
||||
let v = unsafe { std::ffi::CStr::from_ptr(v) };
|
||||
if let Ok(v) = v.to_str() {
|
||||
return Some(v);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn to_utf8<'b>(&self, buf: &'b mut [u8]) -> Option<&'b str> {
|
||||
// SAFETY: `StringRef` is only ever created with a valid `CFStringRef`.
|
||||
let length = unsafe { CFStringGetLength(self.string) };
|
||||
|
||||
let mut buf_bytes = 0;
|
||||
let range = CFRange {
|
||||
location: 0,
|
||||
length,
|
||||
};
|
||||
|
||||
let converted_bytes = unsafe {
|
||||
// SAFETY: `StringRef` is only ever created with a valid `CFStringRef`.
|
||||
CFStringGetBytes(
|
||||
self.string,
|
||||
range,
|
||||
kCFStringEncodingUTF8,
|
||||
b'\0',
|
||||
false as Boolean,
|
||||
buf.as_mut_ptr(),
|
||||
buf.len() as isize,
|
||||
&mut buf_bytes,
|
||||
)
|
||||
};
|
||||
if converted_bytes != length {
|
||||
return None;
|
||||
}
|
||||
|
||||
let len = buf_bytes.try_into().ok()?;
|
||||
let s = buf.get(..len)?;
|
||||
std::str::from_utf8(s).ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
25
vendor/iana-time-zone/src/tz_netbsd.rs
vendored
Normal file
25
vendor/iana-time-zone/src/tz_netbsd.rs
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
use std::fs::read_link;
|
||||
|
||||
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
|
||||
// see https://www.cyberciti.biz/faq/openbsd-time-zone-howto/
|
||||
|
||||
// This is a backport of the Linux implementation.
|
||||
// NetBSDs is less than thorough how the softlink should be set up.
|
||||
|
||||
const PREFIXES: &[&str] = &[
|
||||
"/usr/share/zoneinfo/", // absolute path
|
||||
"../usr/share/zoneinfo/", // relative path
|
||||
];
|
||||
let mut s = read_link("/etc/localtime")?
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.map_err(|_| crate::GetTimezoneError::FailedParsingString)?;
|
||||
for &prefix in PREFIXES {
|
||||
if s.starts_with(prefix) {
|
||||
// Trim to the correct length without allocating.
|
||||
s.replace_range(..prefix.len(), "");
|
||||
return Ok(s);
|
||||
}
|
||||
}
|
||||
Err(crate::GetTimezoneError::FailedParsingString)
|
||||
}
|
||||
21
vendor/iana-time-zone/src/tz_wasm32.rs
vendored
Normal file
21
vendor/iana-time-zone/src/tz_wasm32.rs
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
use js_sys::{Array, Intl, Object, Reflect};
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
|
||||
let intl = Intl::DateTimeFormat::new(&Array::new(), &Object::new()).resolved_options();
|
||||
Reflect::get(&intl, &JsValue::from_str("timeZone"))
|
||||
.ok()
|
||||
.and_then(|tz| tz.as_string())
|
||||
.ok_or(crate::GetTimezoneError::OsError)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn pass() {
|
||||
let tz = super::get_timezone_inner().unwrap();
|
||||
console_log!("tz={:?}", tz);
|
||||
}
|
||||
}
|
||||
180
vendor/iana-time-zone/src/tz_windows.rs
vendored
Normal file
180
vendor/iana-time-zone/src/tz_windows.rs
vendored
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
use std::{io, mem, ptr, slice, sync};
|
||||
|
||||
use winapi::ctypes::wchar_t;
|
||||
use winapi::shared::winerror::{CO_E_NOTINITIALIZED, FAILED, HRESULT};
|
||||
use winapi::um::combaseapi::CoIncrementMTAUsage;
|
||||
use winapi::um::unknwnbase::IUnknown;
|
||||
use winapi::winrt::activation::IActivationFactory;
|
||||
use winapi::winrt::hstring::{HSTRING, HSTRING_HEADER};
|
||||
use winapi::winrt::inspectable::IInspectable;
|
||||
use winapi::winrt::roapi::RoGetActivationFactory;
|
||||
use winapi::winrt::winstring::{
|
||||
WindowsCreateStringReference, WindowsDeleteString, WindowsGetStringRawBuffer,
|
||||
};
|
||||
use winapi::Interface;
|
||||
|
||||
use self::interfaces::ITimeZoneOnCalendar;
|
||||
|
||||
macro_rules! wstring {
|
||||
($($letters:tt)+) => {
|
||||
[ $($letters as wchar_t,)+ ]
|
||||
};
|
||||
}
|
||||
|
||||
const WINDOWS_GLOBALIZATION_CALENDAR: &[wchar_t] = &wstring!(
|
||||
'W' 'i' 'n' 'd' 'o' 'w' 's' '.'
|
||||
'G' 'l' 'o' 'b' 'a' 'l' 'i' 'z' 'a' 't' 'i' 'o' 'n' '.'
|
||||
'C' 'a' 'l' 'e' 'n' 'd' 'a' 'r'
|
||||
0
|
||||
);
|
||||
|
||||
static INITIALIZED: sync::Once = sync::Once::new();
|
||||
static mut FACTORY: Result<Unknown<IActivationFactory>, HRESULT> = Err(0);
|
||||
|
||||
#[repr(transparent)]
|
||||
struct HString(HSTRING);
|
||||
|
||||
#[repr(transparent)]
|
||||
struct Unknown<T>(*mut T);
|
||||
|
||||
impl std::convert::From<HRESULT> for crate::GetTimezoneError {
|
||||
fn from(orig: HRESULT) -> Self {
|
||||
io::Error::from_raw_os_error(orig).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
|
||||
unsafe { Ok(get_timezone()?) }
|
||||
}
|
||||
|
||||
unsafe fn get_timezone() -> Result<String, HRESULT> {
|
||||
// This function crates a Windows.Globalization.Calendar, gets its ITimeZoneOnCalendar, and
|
||||
// then the name of the timezone.
|
||||
|
||||
// We memorize the calendar constructor instead of an instance, because the user could
|
||||
// change their timezone during the execution of the program. Caching the constructor makes
|
||||
// the stress-test example run about 3% faster.
|
||||
|
||||
INITIALIZED.call_once(|| initialize_factory());
|
||||
let factory = match FACTORY {
|
||||
Ok(ref factory) => factory,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
|
||||
let mut calendar: Unknown<IInspectable> = mem::zeroed();
|
||||
let hr = (*factory.0).ActivateInstance(mem::transmute(&mut calendar));
|
||||
if FAILED(hr) {
|
||||
return Err(hr);
|
||||
}
|
||||
|
||||
let mut tz_on_caledar: Unknown<ITimeZoneOnCalendar> = mem::zeroed();
|
||||
let hr = (*calendar.0).QueryInterface(
|
||||
&ITimeZoneOnCalendar::uuidof(),
|
||||
mem::transmute(&mut tz_on_caledar),
|
||||
);
|
||||
drop(calendar);
|
||||
if FAILED(hr) {
|
||||
return Err(hr);
|
||||
}
|
||||
|
||||
let mut timezone: HString = mem::zeroed();
|
||||
let hr = (*tz_on_caledar.0).GetTimeZone(mem::transmute(&mut timezone));
|
||||
drop(tz_on_caledar);
|
||||
if FAILED(hr) {
|
||||
return Err(hr);
|
||||
}
|
||||
|
||||
let mut len = 0;
|
||||
let buf = WindowsGetStringRawBuffer(timezone.0, &mut len);
|
||||
Ok(String::from_utf16_lossy(slice::from_raw_parts(
|
||||
&*buf,
|
||||
len as usize,
|
||||
)))
|
||||
}
|
||||
|
||||
unsafe fn initialize_factory() {
|
||||
// Some other liberary could have called CoIncrementMTAUsage() or CoInitializeEx(), so we only
|
||||
// call CoIncrementMTAUsage() if RoGetActivationFactory() tells us that multithreading was not
|
||||
// initialized, yet.
|
||||
|
||||
let mut h_class_name: HString = mem::zeroed();
|
||||
let mut string_header: HSTRING_HEADER = mem::zeroed();
|
||||
let hr = WindowsCreateStringReference(
|
||||
WINDOWS_GLOBALIZATION_CALENDAR.as_ptr(),
|
||||
(WINDOWS_GLOBALIZATION_CALENDAR.len() - 1) as _,
|
||||
&mut string_header as *mut _,
|
||||
mem::transmute(&mut h_class_name),
|
||||
);
|
||||
if FAILED(hr) {
|
||||
mem::swap(&mut FACTORY, &mut Err(hr));
|
||||
return;
|
||||
}
|
||||
|
||||
let mut factory: Unknown<IActivationFactory> = mem::zeroed();
|
||||
let hr = RoGetActivationFactory(
|
||||
h_class_name.0,
|
||||
&IActivationFactory::uuidof(),
|
||||
mem::transmute(&mut factory),
|
||||
);
|
||||
if !FAILED(hr) {
|
||||
mem::swap(&mut FACTORY, &mut Ok(factory));
|
||||
return;
|
||||
} else if hr != CO_E_NOTINITIALIZED {
|
||||
mem::swap(&mut FACTORY, &mut Err(hr));
|
||||
return;
|
||||
}
|
||||
|
||||
// No need to check the error. The only conceivable error code this function returns is
|
||||
// E_OUTOFMEMORY, and the program is about to get OOM killed anyway in this case. Windows-rs
|
||||
// does not check the result, either.
|
||||
let mut cookie = mem::zeroed();
|
||||
let _ = CoIncrementMTAUsage(&mut cookie);
|
||||
|
||||
let mut factory: Unknown<IActivationFactory> = mem::zeroed();
|
||||
let hr = RoGetActivationFactory(
|
||||
h_class_name.0,
|
||||
&IActivationFactory::uuidof(),
|
||||
mem::transmute(&mut factory),
|
||||
);
|
||||
match !FAILED(hr) {
|
||||
true => mem::swap(&mut FACTORY, &mut Ok(factory)),
|
||||
false => mem::swap(&mut FACTORY, &mut Err(hr)),
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for HString {
|
||||
fn drop(&mut self) {
|
||||
let string = mem::replace(&mut self.0, ptr::null_mut());
|
||||
if !string.is_null() {
|
||||
unsafe { WindowsDeleteString(string) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for Unknown<T> {
|
||||
fn drop(&mut self) {
|
||||
let instance = mem::replace(&mut self.0, ptr::null_mut());
|
||||
if !instance.is_null() {
|
||||
unsafe { (*(instance as *mut IUnknown)).Release() };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case, non_camel_case_types)]
|
||||
mod interfaces {
|
||||
use winapi::shared::minwindef::DWORD;
|
||||
use winapi::shared::winerror::HRESULT;
|
||||
use winapi::winrt::hstring::HSTRING;
|
||||
use winapi::winrt::inspectable::{IInspectable, IInspectableVtbl};
|
||||
use winapi::RIDL;
|
||||
|
||||
RIDL! {
|
||||
#[uuid(0xbb3c25e5, 0x46cf, 0x4317, 0xa3, 0xf5, 0x02, 0x62, 0x1a, 0xd5, 0x44, 0x78)]
|
||||
interface ITimeZoneOnCalendar(ITimeZoneOnCalendar_Vtbl): IInspectable(IInspectableVtbl) {
|
||||
fn GetTimeZone(result: &mut HSTRING,) -> HRESULT,
|
||||
fn ChangeTimeZone(timezoneid: HSTRING,) -> HRESULT,
|
||||
fn TimeZoneAsFullString(result: &mut HSTRING,) -> HRESULT,
|
||||
fn TimeZoneAsString(ideallength: &DWORD, result: &mut HSTRING,) -> HRESULT,
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue