Auto-delete logs finished + custom App-to-VPN messages
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
5404AEEB24A90717003B2F54 /* PrefsShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E67E4524A8B0FE0025D261 /* PrefsShared.swift */; };
|
||||||
540E6780242D2CF100871BBE /* VCRecordings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540E677F242D2CF100871BBE /* VCRecordings.swift */; };
|
540E6780242D2CF100871BBE /* VCRecordings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540E677F242D2CF100871BBE /* VCRecordings.swift */; };
|
||||||
540E67822433483D00871BBE /* VCEditRecording.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540E67812433483D00871BBE /* VCEditRecording.swift */; };
|
540E67822433483D00871BBE /* VCEditRecording.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540E67812433483D00871BBE /* VCEditRecording.swift */; };
|
||||||
540E67842433FAFE00871BBE /* TVCPreviousRecords.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540E67832433FAFE00871BBE /* TVCPreviousRecords.swift */; };
|
540E67842433FAFE00871BBE /* TVCPreviousRecords.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540E67832433FAFE00871BBE /* TVCPreviousRecords.swift */; };
|
||||||
@@ -917,6 +918,7 @@
|
|||||||
54CA02722426B2FD003A5E04 /* IPInterval.swift in Sources */,
|
54CA02722426B2FD003A5E04 /* IPInterval.swift in Sources */,
|
||||||
54CA029A2426B2FD003A5E04 /* Observer.swift in Sources */,
|
54CA029A2426B2FD003A5E04 /* Observer.swift in Sources */,
|
||||||
54CA025C2426B2FD003A5E04 /* ConnectSession.swift in Sources */,
|
54CA025C2426B2FD003A5E04 /* ConnectSession.swift in Sources */,
|
||||||
|
5404AEEB24A90717003B2F54 /* PrefsShared.swift in Sources */,
|
||||||
54CA02C42426DCCD003A5E04 /* GCDAsyncUdpSocket.m in Sources */,
|
54CA02C42426DCCD003A5E04 /* GCDAsyncUdpSocket.m in Sources */,
|
||||||
54CA02BA2426B2FD003A5E04 /* ProxySocket.swift in Sources */,
|
54CA02BA2426B2FD003A5E04 /* ProxySocket.swift in Sources */,
|
||||||
54CA025E2426B2FD003A5E04 /* ResponseGeneratorFactory.swift in Sources */,
|
54CA025E2426B2FD003A5E04 /* ResponseGeneratorFactory.swift in Sources */,
|
||||||
|
|||||||
@@ -80,18 +80,26 @@ class LDObserverFactory: ObserverFactory {
|
|||||||
|
|
||||||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
|
|
||||||
let proxyServerPort: UInt16 = 9090
|
private let proxyServerPort: UInt16 = 9090
|
||||||
let proxyServerAddress = "127.0.0.1"
|
private let proxyServerAddress = "127.0.0.1"
|
||||||
var proxyServer: GCDHTTPProxyServer!
|
private var proxyServer: GCDHTTPProxyServer!
|
||||||
|
|
||||||
|
private var autoDeleteTimer: Timer? = nil
|
||||||
|
|
||||||
|
private func reloadSettings() {
|
||||||
|
reloadDomainFilter()
|
||||||
|
setAutoDelete(PrefsShared.AutoDeleteLogsDays)
|
||||||
|
}
|
||||||
|
|
||||||
override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
|
override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
|
||||||
|
DDLogVerbose("startTunnel with with options: \(String(describing: options))")
|
||||||
do {
|
do {
|
||||||
try SQLiteDatabase.open().initCommonScheme()
|
try SQLiteDatabase.open().initCommonScheme()
|
||||||
} catch {
|
} catch {
|
||||||
completionHandler(error)
|
completionHandler(error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
reloadDomainFilter()
|
reloadSettings()
|
||||||
|
|
||||||
if proxyServer != nil {
|
if proxyServer != nil {
|
||||||
proxyServer.stop()
|
proxyServer.stop()
|
||||||
@@ -145,12 +153,63 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
proxyServer = nil
|
proxyServer = nil
|
||||||
filterDomains = nil
|
filterDomains = nil
|
||||||
filterOptions = nil
|
filterOptions = nil
|
||||||
|
autoDeleteTimer?.fire() // one last time before we quit
|
||||||
|
autoDeleteTimer?.invalidate()
|
||||||
completionHandler()
|
completionHandler()
|
||||||
exit(EXIT_SUCCESS)
|
exit(EXIT_SUCCESS)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
|
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
|
||||||
reloadDomainFilter()
|
let message = String(data: messageData, encoding: .utf8)
|
||||||
|
if let msg = message, let i = msg.firstIndex(of: ":") {
|
||||||
|
let action = msg.prefix(upTo: i)
|
||||||
|
let value = msg.suffix(from: msg.index(after: i))
|
||||||
|
switch action {
|
||||||
|
case "filter-update":
|
||||||
|
reloadDomainFilter() // TODO: reload only selected domain?
|
||||||
|
return
|
||||||
|
case "auto-delete":
|
||||||
|
setAutoDelete(Int(value) ?? PrefsShared.AutoDeleteLogsDays)
|
||||||
|
return
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DDLogWarn("This should never happen! Received unknown handleAppMessage: \(message ?? messageData.base64EncodedString())")
|
||||||
|
reloadSettings() // just in case we fallback to do everything
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ################################################################
|
||||||
|
// #
|
||||||
|
// # MARK: - Auto-delete Timer
|
||||||
|
// #
|
||||||
|
// ################################################################
|
||||||
|
|
||||||
|
extension PacketTunnelProvider {
|
||||||
|
|
||||||
|
private func setAutoDelete(_ days: Int) {
|
||||||
|
autoDeleteTimer?.invalidate()
|
||||||
|
guard days > 0 else { return }
|
||||||
|
// Repeat interval uses days as hours. min 1 hr, max 24 hrs.
|
||||||
|
let interval = TimeInterval(min(24, days) * 60 * 60)
|
||||||
|
autoDeleteTimer = Timer.scheduledTimer(timeInterval: interval,
|
||||||
|
target: self, selector: #selector(autoDeleteNow),
|
||||||
|
userInfo: days, repeats: true)
|
||||||
|
autoDeleteTimer!.fire()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func autoDeleteNow(_ sender: Timer) {
|
||||||
|
DDLogInfo("Auto-delete old logs")
|
||||||
|
queue.async {
|
||||||
|
do {
|
||||||
|
try AppDB?.dnsLogsDeleteOlderThan(days: sender.userInfo as! Int)
|
||||||
|
} catch {
|
||||||
|
DDLogWarn("Couldn't delete logs, will retry in 5 minutes. \(error)")
|
||||||
|
if sender.isValid {
|
||||||
|
sender.fireDate = Date().addingTimeInterval(300) // retry in 5 min
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,4 +22,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
sync.start()
|
sync.start()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||||
|
TheGreatDestroyer.deleteLogs(olderThan: PrefsShared.AutoDeleteLogsDays)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,9 +39,21 @@ extension SQLiteDatabase {
|
|||||||
/// `INSERT INTO cache (dns, opt) VALUES (?, ?);`
|
/// `INSERT INTO cache (dns, opt) VALUES (?, ?);`
|
||||||
func logWrite(_ domain: String, blocked: Bool = false) throws {
|
func logWrite(_ domain: String, blocked: Bool = false) throws {
|
||||||
try self.run(sql: "INSERT INTO cache (dns, opt) VALUES (?, ?);",
|
try self.run(sql: "INSERT INTO cache (dns, opt) VALUES (?, ?);",
|
||||||
bind: [BindText(domain), BindInt32(blocked ? 1 : 0)])
|
bind: [BindText(domain), BindInt32(blocked ? 1 : 0)])
|
||||||
{ try ifStep($0, SQLITE_DONE) }
|
{ try ifStep($0, SQLITE_DONE) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `DELETE FROM cache WHERE ts < (now - ? days);`
|
||||||
|
/// - Parameter days: if `0` or negative, this function does nothing.
|
||||||
|
/// - Returns: `true` if at least one row was deleted.
|
||||||
|
@discardableResult func dnsLogsDeleteOlderThan(days: Int) throws -> Bool {
|
||||||
|
guard days > 0 else { return false }
|
||||||
|
return try self.run(sql: "DELETE FROM cache WHERE ts < strftime('%s', 'now', ?);",
|
||||||
|
bind: [BindText("-\(days) days")]) {
|
||||||
|
try ifStep($0, SQLITE_DONE)
|
||||||
|
return numberOfChanges > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ struct TheGreatDestroyer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fired when user taps on Settings -> Delete All Logs
|
/// Fired when user taps on Settings -> "Delete All Logs"
|
||||||
static func deleteAllLogs() {
|
static func deleteAllLogs() {
|
||||||
sync.pause()
|
sync.pause()
|
||||||
DispatchQueue.global().async {
|
DispatchQueue.global().async {
|
||||||
@@ -26,4 +26,18 @@ struct TheGreatDestroyer {
|
|||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fired when user changes Settings -> "Auto-delete logs" and every time the App enters foreground
|
||||||
|
static func deleteLogs(olderThan days: Int) {
|
||||||
|
guard days > 0 else { return }
|
||||||
|
sync.pause()
|
||||||
|
DispatchQueue.global().async {
|
||||||
|
defer { sync.continue() }
|
||||||
|
QLog.Info("Auto-delete logs")
|
||||||
|
guard let success = try? AppDB?.dnsLogsDeleteOlderThan(days: days), success else {
|
||||||
|
return // nothing changed
|
||||||
|
}
|
||||||
|
sync.needsReloadDB()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,19 @@ final class GlassVPNManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Notify VPN extension about changes
|
||||||
|
/// - Returns: `true` on success, `false` if VPN is off or message could not be converted to `.utf8`
|
||||||
|
@discardableResult func send(_ message: VPNAppMessage) -> Bool {
|
||||||
|
if let session = self.managerVPN?.connection as? NETunnelProviderSession,
|
||||||
|
session.status == .connected, let data = message.raw {
|
||||||
|
do {
|
||||||
|
try session.sendProviderMessage(data, responseHandler: nil)
|
||||||
|
return true
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Notify callback
|
// MARK: - Notify callback
|
||||||
|
|
||||||
@@ -50,12 +63,8 @@ final class GlassVPNManager {
|
|||||||
postRawVPNState((notification.object as? NETunnelProviderSession)?.status ?? .invalid)
|
postRawVPNState((notification.object as? NETunnelProviderSession)?.status ?? .invalid)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func didChangeDomainFilter() {
|
@objc private func didChangeDomainFilter(_ notification: Notification) {
|
||||||
// Notify VPN extension about changes
|
send(.filterUpdate(domain: notification.object as? String))
|
||||||
if let session = self.managerVPN?.connection as? NETunnelProviderSession,
|
|
||||||
session.status == .connected {
|
|
||||||
try? session.sendProviderMessage("filter-update".data(using: .ascii)!, responseHandler: nil)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -109,3 +118,22 @@ final class GlassVPNManager {
|
|||||||
NotifyVPNStateChanged.post()
|
NotifyVPNStateChanged.post()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// |
|
||||||
|
// | MARK: - VPN message
|
||||||
|
// |
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
|
struct VPNAppMessage {
|
||||||
|
let raw: Data?
|
||||||
|
init(_ string: String) { raw = string.data(using: .utf8) }
|
||||||
|
|
||||||
|
static func filterUpdate(domain: String? = nil) -> Self {
|
||||||
|
.init("filter-update:\(domain ?? "")")
|
||||||
|
}
|
||||||
|
static func autoDelete(after interval: Int) -> Self {
|
||||||
|
.init("auto-delete:\(interval)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -77,14 +77,19 @@ class TVCSettings: UITableViewController {
|
|||||||
|
|
||||||
let picker = DurationPickerAlert(
|
let picker = DurationPickerAlert(
|
||||||
title: "Auto-delete logs",
|
title: "Auto-delete logs",
|
||||||
detail: "Logs will be deleted on app launch or periodically as long as the VPN is running.",
|
detail: "Warning: Logs older than the selected interval are deleted immediately! " +
|
||||||
|
"Logs are also deleted on each app launch, and periodically in the background as long as the VPN is running.",
|
||||||
options: [(0...30).map{"\($0)"}, ["Days", "Weeks", "Months"]],
|
options: [(0...30).map{"\($0)"}, ["Days", "Weeks", "Months"]],
|
||||||
widths: [0.4, 0.6])
|
widths: [0.4, 0.6])
|
||||||
picker.pickerView.setSelection([min(30, one), two])
|
picker.pickerView.setSelection([min(30, one), two])
|
||||||
picker.present(in: self) {
|
picker.present(in: self) { _, idx in
|
||||||
PrefsShared.AutoDeleteLogsDays = $1[0] * multiplier[$1[1]]
|
cell.detailTextLabel?.text = autoDeleteString(idx[0], unit: idx[1])
|
||||||
cell.detailTextLabel?.text = autoDeleteString($1[0], unit: $1[1])
|
let asDays = idx[0] * multiplier[idx[1]]
|
||||||
// TODO: notify VPN and local delete timer
|
PrefsShared.AutoDeleteLogsDays = asDays
|
||||||
|
if !GlassVPN.send(.autoDelete(after: asDays)) {
|
||||||
|
// if VPN isn't active, fallback to immediate local delete
|
||||||
|
TheGreatDestroyer.deleteLogs(olderThan: asDays)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user