Skip to content

Commit

Permalink
WIP new weather API
Browse files Browse the repository at this point in the history
Cleanup of linter warnings...
Fix Countdown and Timer that weren't using the override locale
Message shell scripts are now ran async
  • Loading branch information
glouel committed Mar 5, 2021
1 parent e1f655c commit c908d8a
Show file tree
Hide file tree
Showing 23 changed files with 483 additions and 197 deletions.
16 changes: 12 additions & 4 deletions Aerial.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,9 @@
03A596D623AA752F0097EA66 /* InfoClockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A596D423AA752F0097EA66 /* InfoClockView.swift */; };
03A596D723AA75490097EA66 /* InfoMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A596D223AA750F0097EA66 /* InfoMessageView.swift */; };
03A596D923AB8F000097EA66 /* InfoLocationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A596D823AB8F000097EA66 /* InfoLocationView.swift */; };
03A6D14625F109B900960135 /* OpenWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A6D14525F109B900960135 /* OpenWeather.swift */; };
03A6D14725F109B900960135 /* OpenWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A6D14525F109B900960135 /* OpenWeather.swift */; };
03A6D14825F109B900960135 /* OpenWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A6D14525F109B900960135 /* OpenWeather.swift */; };
03AA7A5D24C84C6300A47970 /* cloud.bolt.rain.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 03AA7A2524C84C6200A47970 /* cloud.bolt.rain.pdf */; };
03AA7A5E24C84C6300A47970 /* cloud.bolt.rain.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 03AA7A2524C84C6200A47970 /* cloud.bolt.rain.pdf */; };
03AA7A5F24C84C6300A47970 /* cloud.bolt.rain.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 03AA7A2524C84C6200A47970 /* cloud.bolt.rain.pdf */; };
Expand Down Expand Up @@ -1068,6 +1071,7 @@
03A596D223AA750F0097EA66 /* InfoMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoMessageView.swift; sourceTree = "<group>"; };
03A596D423AA752F0097EA66 /* InfoClockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoClockView.swift; sourceTree = "<group>"; };
03A596D823AB8F000097EA66 /* InfoLocationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoLocationView.swift; sourceTree = "<group>"; };
03A6D14525F109B900960135 /* OpenWeather.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenWeather.swift; sourceTree = "<group>"; };
03AA7A2524C84C6200A47970 /* cloud.bolt.rain.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = cloud.bolt.rain.pdf; sourceTree = "<group>"; };
03AA7A3C24C84C6200A47970 /* wind.snow.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = wind.snow.pdf; sourceTree = "<group>"; };
03AA7A3D24C84C6200A47970 /* snow.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = snow.pdf; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1523,6 +1527,7 @@
0385FC58242B9AE1007E6513 /* APISecrets.swift */,
03D2A43324474304003ABD30 /* YahooWeatherAPI.swift */,
03D2A43D24476117003ABD30 /* WeatherAPI.swift */,
03A6D14525F109B900960135 /* OpenWeather.swift */,
);
path = API;
sourceTree = "<group>";
Expand Down Expand Up @@ -2615,6 +2620,7 @@
031945F424CCC48C00F37B35 /* CreditsViewController.swift in Sources */,
0396D56224B8B7ED00CC021B /* InfoCountdownView.swift in Sources */,
0396D56324B8B7ED00CC021B /* ConditionSymbolLayer.swift in Sources */,
03A6D14725F109B900960135 /* OpenWeather.swift in Sources */,
0396D56424B8B7ED00CC021B /* CheckboxCellView.swift in Sources */,
03933B8C24C3986800A98D94 /* SourcesViewController.swift in Sources */,
034DEE2F24BF1BC700A2D3CD /* PanelWindowController.swift in Sources */,
Expand Down Expand Up @@ -2724,6 +2730,7 @@
0374C9FF247AC5BC002F29D3 /* Locations.swift in Sources */,
036A57DC23F5828E0009DC02 /* CountdownLayer.swift in Sources */,
0313F9EA2294338300B074BB /* CustomVideoController.swift in Sources */,
03A6D14825F109B900960135 /* OpenWeather.swift in Sources */,
FAC36F5A1BE1756D007F2A20 /* AerialVideo.swift in Sources */,
038D2EBD23AB91C300CD91F7 /* InfoLocationView.swift in Sources */,
034F29B823A7A9B3004B34D5 /* InfoTableSource.swift in Sources */,
Expand Down Expand Up @@ -2874,6 +2881,7 @@
031945F324CCC48C00F37B35 /* CreditsViewController.swift in Sources */,
036A57D823F470940009DC02 /* InfoCountdownView.swift in Sources */,
033D68812453080C0016F837 /* ConditionSymbolLayer.swift in Sources */,
03A6D14625F109B900960135 /* OpenWeather.swift in Sources */,
03C344FC24B778EE00906EA6 /* CheckboxCellView.swift in Sources */,
03933B8B24C3986800A98D94 /* SourcesViewController.swift in Sources */,
034DEE2E24BF1BC700A2D3CD /* PanelWindowController.swift in Sources */,
Expand Down Expand Up @@ -3180,15 +3188,15 @@
CODE_SIGN_IDENTITY = "Developer ID Application: Guillaume Louel (3L54M5L5KK)";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 2.2.8;
CURRENT_PROJECT_VERSION = 2.2.8beta2;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 3L54M5L5KK;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = "$(SRCROOT)/Resources/Old stuff/Info.plist";
INSTALL_PATH = "$(HOME)/Library/Screen Savers";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.12;
MARKETING_VERSION = 2.2.8;
MARKETING_VERSION = 2.2.8beta2;
PRODUCT_BUNDLE_IDENTIFIER = com.johncoates.Aerial;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand All @@ -3208,15 +3216,15 @@
CODE_SIGN_IDENTITY = "Developer ID Application: Guillaume Louel (3L54M5L5KK)";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 2.2.8;
CURRENT_PROJECT_VERSION = 2.2.8beta2;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 3L54M5L5KK;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = "$(SRCROOT)/Resources/Old stuff/Info.plist";
INSTALL_PATH = "$(HOME)/Library/Screen Savers";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.12;
MARKETING_VERSION = 2.2.8;
MARKETING_VERSION = 2.2.8beta2;
OTHER_CODE_SIGN_FLAGS = "--timestamp";
PRODUCT_BUNDLE_IDENTIFIER = com.johncoates.Aerial;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down
2 changes: 1 addition & 1 deletion Aerial/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
super.init()

// First thing : let our model know we are an app and not a screensaver !
Aerial.instance.appMode = true
Aerial.appMode = true

let panelWindowController = PanelWindowController()
panelWindowController.showWindow(self)
Expand Down
220 changes: 220 additions & 0 deletions Aerial/Source/Models/API/OpenWeather.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
//
// OpenWeather.swift
// Aerial
//
// Created by Guillaume Louel on 04/03/2021.
// Copyright © 2021 Guillaume Louel. All rights reserved.
//
// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
// let openWeather = try? newJSONDecoder().decode(OWeather.self, from: jsonData)

import Foundation

enum NetworkError: Error {
case badURL
case requestFailed
case unknown
}

// MARK: - OpenWeather
struct OWeather: Codable {
let coord: OWCoord?
let weather: [OWWeather]?
let base: String?
let main: OWMain?
let visibility: Int?
let wind: OWWind?
let clouds: OWClouds?
let dt: Int?
let sys: OWSys?
let timezone, id: Int?
let name: String?
let cod: Int?
}

// MARK: - OWClouds
struct OWClouds: Codable {
let all: Int?
}

// MARK: - OWCoord
struct OWCoord: Codable {
let lon, lat: Double?
}
// MARK: - OWMain
struct OWMain: Codable {
let temp: Double
let feelsLike: Double
let tempMin, tempMax, pressure, humidity: Double

enum CodingKeys: String, CodingKey {
case temp
case feelsLike = "feels_like"
case tempMin = "temp_min"
case tempMax = "temp_max"
case pressure, humidity
}
}

// MARK: - OWSys
struct OWSys: Codable {
let type, id: Int
let country: String
let sunrise, sunset: Int
}

// MARK: - OWWeather
struct OWWeather: Codable {
let id: Int
let main, weatherDescription, icon: String

enum CodingKeys: String, CodingKey {
case id, main
case weatherDescription = "description"
case icon
}
}

// MARK: - OWWind
struct OWWind: Codable {
let speed: Double
let deg: Int
}

struct OpenWeather {

static func getUnits() -> String {
if PrefsInfo.weather.degree == .celsius {
return "metric"
} else {
return "imperial"
}
}

static func getShortcodeLanguage() -> String {
let preferences = Preferences.sharedInstance

// Those are the languages supported by OpenWeather
let weatherLanguages = ["af", "al", "ar", "az", "bg", "ca", "cz", "da", "de", "el", "en",
"eu", "fa", "fi", "fr", "gl", "he", "hi", "hr", "hu", "id", "it",
"ja", "kr", "la", "lt", "mk", "no", "nl", "pl", "pt", "pt_br", "ro",
"ru", "sv", "sk", "sl", "es", "sr", "th", "tr", "uk", "vi", "zh_cn",
"zh_tw", "zu", ]

if preferences.ciOverrideLanguage == "" {
let bestMatchedLanguage = Bundle.preferredLocalizations(from: weatherLanguages, forPreferences: Locale.preferredLanguages).first
if let match = bestMatchedLanguage {
debugLog("Best matched language : \(match)")
return match
}
} else {
debugLog("Overrode matched language : \(preferences.ciOverrideLanguage!)")
return preferences.ciOverrideLanguage!
}

// We fallback here if nothing works
return "en"
}

static func makeUrl(lat: String, lon: String) -> String {
return "http://api.openweathermap.org/data/2.5/weather"
+ "?lat=\(lat)&lon=\(lon)"
+ "&units=\(getUnits())"
+ "&lang=\(getShortcodeLanguage())"
+ "&APPID=\(APISecrets.openWeatherAppId)"
}

static func makeUrl(location: String) -> String {
let nloc = location.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!

return "http://api.openweathermap.org/data/2.5/weather"
+ "?q=\(nloc)"
+ "&units=\(getUnits())"
+ "&lang=\(getShortcodeLanguage())"
+ "&APPID=\(APISecrets.openWeatherAppId)"
}

static func fetch(completion: @escaping(Result<OWeather, NetworkError>) -> Void) {
if PrefsInfo.weather.locationMode == .useCurrent {
let location = Locations.sharedInstance

location.getCoordinates(failure: { (_) in
completion(.failure(.unknown))
}, success: { (coordinates) in
let lat = String(format: "%.2f", coordinates.latitude)
let lon = String(format: "%.2f", coordinates.longitude)
debugLog("=== OW: Starting locationMode")

fetchData(from: makeUrl(lat: lat, lon: lon)) { result in
switch result {
case .success(let jsonString):
print(jsonString)
let jsonData = jsonString.data(using: .utf8)!

if let openWeather = try? newJSONDecoder().decode(OWeather.self, from: jsonData) {
completion(.success(openWeather))
} else {
completion(.failure(.unknown))
}
case .failure(let error):
completion(.failure(.unknown))
print(error.localizedDescription)
}
}
})
} else {
// Just in case, we add a failsafe
if PrefsInfo.weather.locationString == "" {
PrefsInfo.weather.locationString = "Paris, FR"
}
debugLog("=== OW: Starting manual mode")

print(makeUrl(location: PrefsInfo.weather.locationString))
fetchData(from: makeUrl(location: PrefsInfo.weather.locationString)) { result in
switch result {
case .success(let jsonString):
print(jsonString)
let jsonData = jsonString.data(using: .utf8)!
do {
let openWeather = try newJSONDecoder().decode(OWeather.self, from: jsonData)
completion(.success(openWeather))
} catch {
print("error : \(error.localizedDescription)")
completion(.failure(.unknown))
}
case .failure(let error):
completion(.failure(.unknown))
print(error.localizedDescription)
}
}

}
}

private static func fetchData(from urlString: String, completion: @escaping (Result<String, NetworkError>) -> Void) {
// check the URL is OK, otherwise return with a failure
guard let url = URL(string: urlString) else {
completion(.failure(.badURL))
return
}

URLSession.shared.dataTask(with: url) { data, _, error in
// the task has completed – push our work back to the main thread
DispatchQueue.main.async {
if let data = data {
// success: convert the data to a string and send it back
let stringData = String(decoding: data, as: UTF8.self)
completion(.success(stringData))
} else if error != nil {
// any sort of network failure
completion(.failure(.requestFailed))
} else {
// this ought not to be possible, yet here we are
completion(.failure(.unknown))
}
}
}.resume()
}
}
23 changes: 19 additions & 4 deletions Aerial/Source/Models/Aerial.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@
// Aerial.swift
// Aerial
//
// Contains some common helpers used throughout the code
//
// Created by Guillaume Louel on 17/07/2020.
// Copyright © 2020 Guillaume Louel. All rights reserved.
//

import Cocoa

class Aerial: NSObject {
// TODO : Uh, better check that's required some day
static let instance = Aerial()

// We use this to track whether we run as a screen saver or an app
var appMode = false
static var appMode = false

// We also track darkmode here now
static var darkMode = false
Expand Down Expand Up @@ -56,6 +55,21 @@ class Aerial: NSObject {
return "Version ?"
}

// Language detection
static func getPreferredLanguage() -> String {
let printOutputLocale: NSLocale = NSLocale(localeIdentifier: Locale.preferredLanguages[0])
if let deviceLanguageName: String = printOutputLocale.displayName(forKey: .identifier, value: Locale.preferredLanguages[0]) {
if #available(OSX 10.12, *) {
return "Preferred language: \(deviceLanguageName) [\(printOutputLocale.languageCode)]"
} else {
return "Preferred language: \(deviceLanguageName)"
}
} else {
return ""
}
}

// Alerts
static func showErrorAlert(question: String, text: String, button: String = "OK") {
let alert = NSAlert()
alert.messageText = question
Expand Down Expand Up @@ -91,6 +105,7 @@ class Aerial: NSObject {
alert.runModal()
}

// Symbol/icon generation
static func getSymbol(_ named: String) -> NSImage? {
if let imagePath = Bundle(for: PanelWindowController.self).path(
forResource: fallbackSymbol(named),
Expand Down
2 changes: 2 additions & 0 deletions Aerial/Source/Models/Cache/Cache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,7 @@ struct Cache {
return evictable.sorted { $0.key < $1.key }.map({ $0.value })
}

// swiftlint:disable:next cyclomatic_complexity
static func freeCache() {
guard PrefsCache.enableManagement else {
return
Expand Down Expand Up @@ -689,3 +690,4 @@ struct Cache {
VideoManager.sharedInstance.queueDownload(rotation.first!)
}
}
// swiftlint:disable:this file_length
1 change: 1 addition & 0 deletions Aerial/Source/Models/Hardware/HardwareDetection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ final class HardwareDetection: NSObject {
}

// Get best suggestion
// swiftlint:disable:next cyclomatic_complexity
func getSuggestedFormat() -> VideoFormat {
switch isHEVCMain10HWDecodingAvailable() {
case .supported:
Expand Down
Loading

0 comments on commit c908d8a

Please sign in to comment.