Skip to content

Commit 3b19186

Browse files
authored
Historical data (#2)
Add DNSQueries historical data
1 parent b3bb4e7 commit 3b19186

File tree

7 files changed

+199
-61
lines changed

7 files changed

+199
-61
lines changed

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,16 @@ SwiftHole.init(host: "192.168.1.123", apiToken: "klaatubaradanikto")
6666
SwiftHole has the following public interface:
6767

6868
```swift
69-
public struct SwiftHole {
7069

71-
public init(host: String, apiToken: String? = nil)
70+
public init(host: String, port: Int? = nil, apiToken: String? = nil)
7271

73-
public func fetchSummary(completion: @escaping (Result<Summary, SwiftHoleError>) -> ())
72+
public func fetchSummary(completion: @escaping (Result<Summary, SwiftHoleError>) -> ())
7473

75-
public func enablePiHole(_ completion: @escaping (Result<Void, SwiftHoleError>) -> ())
74+
public func enablePiHole(_ completion: @escaping (Result<Void, SwiftHoleError>) -> ())
75+
76+
public func disablePiHole(seconds: Int = 0, completion: @escaping (Result<Void, SwiftHoleError>) -> ())
77+
78+
public func fetchHistoricalQueries(completion: @escaping (Result<[DNSRequest], SwiftHoleError>) -> ())
7679

77-
public func disablePiHole(seconds: Int = 0, completion: @escaping (Result<Void, SwiftHoleError>) -> ())
78-
}
7980
```
8081

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// File.swift
3+
//
4+
//
5+
// Created by Fernando Bunn on 05/07/2020.
6+
//
7+
8+
import Foundation
9+
10+
public struct DNSRequest {
11+
let date: Date
12+
public let adsCount: Int
13+
public let requestCount: Int
14+
15+
public var startDate: Date {
16+
date - TimeInterval(300)
17+
}
18+
19+
public var endDate: Date {
20+
date + TimeInterval(299)
21+
}
22+
23+
public var permittedRequests: Int {
24+
requestCount - adsCount
25+
}
26+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// HistoricalData.swift
3+
//
4+
//
5+
// Created by Fernando Bunn on 05/07/2020.
6+
//
7+
8+
import Foundation
9+
10+
struct HistoricalQueries: Decodable {
11+
let domainsOverTime, adsOverTime: [String: Int]
12+
let requests: [DNSRequest]
13+
14+
enum CodingKeys: String, CodingKey {
15+
case domainsOverTime = "domains_over_time"
16+
case adsOverTime = "ads_over_time"
17+
}
18+
19+
public init(from decoder: Decoder) throws {
20+
let values = try decoder.container(keyedBy: CodingKeys.self)
21+
domainsOverTime = try values.decode([String: Int].self, forKey: .domainsOverTime)
22+
let ads = try values.decode([String: Int].self, forKey: .adsOverTime)
23+
24+
requests = domainsOverTime.map { key, value in
25+
let date = Date(timeIntervalSince1970: Double(key) ?? 0)
26+
return DNSRequest(date: date, adsCount: ads[key] ?? 0, requestCount: value)
27+
}.sorted(by: { $0.date < $1.date })
28+
adsOverTime = ads
29+
}
30+
}

Sources/SwiftHole/Model/Status.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// File.swift
2+
// Status.swift
33
//
44
//
55
// Created by Fernando Bunn on 10/05/2020.

Sources/SwiftHole/Service/Router.swift

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ internal enum Router {
1212
case getLogs(Environment)
1313
case disable(Environment, Int)
1414
case enable(Environment)
15+
case getHistoricalQueries(Environment)
1516

1617
var scheme: String {
1718
switch self {
1819
case .getSummary,
1920
.getLogs,
2021
.disable,
21-
.enable:
22+
.enable,
23+
.getHistoricalQueries:
2224
return "http"
2325
}
2426
}
@@ -28,7 +30,8 @@ internal enum Router {
2830
case .getSummary(let environment),
2931
.getLogs (let environment),
3032
.disable(let environment, _),
31-
.enable (let environment):
33+
.enable (let environment),
34+
.getHistoricalQueries(let environment):
3235
return environment.host
3336
}
3437
}
@@ -38,17 +41,19 @@ internal enum Router {
3841
case .getSummary,
3942
.getLogs,
4043
.disable,
41-
.enable:
44+
.enable,
45+
.getHistoricalQueries:
4246
return "/admin/api.php"
4347
}
4448
}
45-
49+
4650
var port: Int? {
4751
switch self {
4852
case .getSummary(let environment),
4953
.getLogs (let environment),
5054
.disable(let environment, _),
51-
.enable (let environment):
55+
.enable (let environment),
56+
.getHistoricalQueries (let environment):
5257
return environment.port
5358
}
5459
}
@@ -58,7 +63,8 @@ internal enum Router {
5863
case .getSummary,
5964
.getLogs,
6065
.disable,
61-
.enable:
66+
.enable,
67+
.getHistoricalQueries:
6268
return "GET"
6369
}
6470
}
@@ -79,6 +85,10 @@ internal enum Router {
7985
case .enable(let environment):
8086
return [URLQueryItem(name: "auth", value: environment.apiToken ?? ""),
8187
URLQueryItem(name: "enable", value: "")]
88+
89+
case .getHistoricalQueries (let environment):
90+
return [URLQueryItem(name: "overTimeData10mins", value: ""),
91+
URLQueryItem(name: "auth", value: environment.apiToken ?? "")]
8292
}
8393
}
8494
}

Sources/SwiftHole/SwiftHole.swift

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public struct SwiftHole {
1010

1111

1212
// MARK: Public Methods
13-
13+
1414
public init(host: String, port: Int? = nil, apiToken: String? = nil) {
1515
environment = Environment(host: host, port: port, apiToken: apiToken)
1616
}
@@ -42,11 +42,22 @@ public struct SwiftHole {
4242
}
4343
}
4444

45-
45+
public func fetchHistoricalQueries(completion: @escaping (Result<[DNSRequest], SwiftHoleError>) -> ()) {
46+
service.request(router: .getHistoricalQueries(environment)) { (result: Result<HistoricalQueries, SwiftHoleError>) in
47+
switch result {
48+
case .success(let historicalQueries):
49+
completion(.success(historicalQueries.requests))
50+
case .failure(let error):
51+
completion(.failure(error))
52+
}
53+
}
54+
}
55+
56+
4657
// MARK: Private Methods
47-
58+
4859
private func handleEnableDisableMethod(type: EnableDisableMethodType, result: Result<Status, SwiftHoleError>, completion: @escaping (Result<Void, SwiftHoleError>) -> ()) {
49-
60+
5061
switch result {
5162
case .success(let status):
5263
if (type == .disable && status.isEnabled == false) ||

Tests/SwiftHoleTests/SwiftHoleTests.swift

Lines changed: 104 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -42,28 +42,27 @@ final class SwiftHoleTests: XCTestCase {
4242
let decoder = JSONDecoder()
4343
do {
4444
let decoded = try decoder.decode(Summary.self, from: data)
45-
XCTAssert(decoded.domainsBeingBlocked == 276319)
46-
XCTAssert(decoded.dnsQueriesToday == 37852)
47-
XCTAssert(decoded.adsBlockedToday == 5356)
48-
XCTAssert(decoded.adsPercentageToday == 14.149847)
49-
XCTAssert(decoded.uniqueDomains == 6863)
50-
XCTAssert(decoded.queriesForwarded == 23015)
51-
XCTAssert(decoded.queriesCached == 6932)
52-
XCTAssert(decoded.clientsEverSeen == 33)
53-
XCTAssert(decoded.uniqueClients == 19)
54-
XCTAssert(decoded.dnsQueriesAllTypes == 37852)
55-
XCTAssert(decoded.replyNODATA == 3097)
56-
XCTAssert(decoded.replyNXDOMAIN == 4675)
57-
XCTAssert(decoded.replyCNAME == 10987)
58-
XCTAssert(decoded.replyIP == 17593)
59-
XCTAssert(decoded.privacyLevel == 0)
60-
XCTAssert(decoded.status == "enabled")
61-
XCTAssert(decoded.gravityLastUpdated.fileExists == true)
62-
XCTAssert(decoded.gravityLastUpdated.absolute == 1589194189)
63-
XCTAssert(decoded.gravityLastUpdated.relative.days == 5)
64-
XCTAssert(decoded.gravityLastUpdated.relative.hours == 1)
65-
XCTAssert(decoded.gravityLastUpdated.relative.minutes == 36)
66-
45+
XCTAssertEqual(decoded.domainsBeingBlocked, 276319, "it should have 276319 domainsBeingBlocked")
46+
XCTAssertEqual(decoded.dnsQueriesToday, 37852, "it should have 37852 dnsQueriesToday")
47+
XCTAssertEqual(decoded.adsBlockedToday, 5356, "it should have 5356 adsBlockedToday")
48+
XCTAssertEqual(decoded.adsPercentageToday, 14.149847, "it should have 14.149847 adsPercentageToday")
49+
XCTAssertEqual(decoded.uniqueDomains, 6863, "it should have 6863 uniqueDomains")
50+
XCTAssertEqual(decoded.queriesForwarded, 23015, "it should have 23015 queriesForwarded")
51+
XCTAssertEqual(decoded.queriesCached, 6932, "it should have 6932 queriesCached")
52+
XCTAssertEqual(decoded.clientsEverSeen, 33, "it should have 33 clientsEverSeen")
53+
XCTAssertEqual(decoded.uniqueClients, 19, "it should have 19 uniqueClients")
54+
XCTAssertEqual(decoded.dnsQueriesAllTypes, 37852, "it should have 37852 dnsQueriesAllTypes")
55+
XCTAssertEqual(decoded.replyNODATA, 3097, "it should have 3097 replyNODATA")
56+
XCTAssertEqual(decoded.replyNXDOMAIN, 4675, "it should have 4675 replyNXDOMAIN")
57+
XCTAssertEqual(decoded.replyCNAME, 10987, "it should have 10987 replyCNAME")
58+
XCTAssertEqual(decoded.replyIP, 17593, "it should have 17593 replyIP")
59+
XCTAssertEqual(decoded.privacyLevel, 0, "it should have 0 privacyLevel")
60+
XCTAssertEqual(decoded.status, "enabled", "it should have status enabled")
61+
XCTAssertEqual(decoded.gravityLastUpdated.fileExists, true, "it should have fileExists true")
62+
XCTAssertEqual(decoded.gravityLastUpdated.absolute, 1589194189, "it should have 1589194189 gravityLastUpdated.absolute")
63+
XCTAssertEqual(decoded.gravityLastUpdated.relative.days, 5, "it should have 5 gravityLastUpdated.relative.days")
64+
XCTAssertEqual(decoded.gravityLastUpdated.relative.hours, 1, "it should have 1 gravityLastUpdated.relative.hours")
65+
XCTAssertEqual(decoded.gravityLastUpdated.relative.minutes, 36, "it should have 36 gravityLastUpdated.relative.minutes")
6766
} catch {
6867
XCTFail("Can't decode test file bundle \(error)")
6968
}
@@ -108,28 +107,89 @@ final class SwiftHoleTests: XCTestCase {
108107
let decoder = JSONDecoder()
109108
do {
110109
let decoded = try decoder.decode(Summary.self, from: data)
111-
XCTAssert(decoded.domainsBeingBlocked == 261271)
112-
XCTAssert(decoded.dnsQueriesToday == 27581)
113-
XCTAssert(decoded.adsBlockedToday == 4295)
114-
XCTAssert(decoded.adsPercentageToday == 15.572314)
115-
XCTAssert(decoded.uniqueDomains == 2760)
116-
XCTAssert(decoded.queriesForwarded == 19663)
117-
XCTAssert(decoded.queriesCached == 3623)
118-
XCTAssert(decoded.clientsEverSeen == 14)
119-
XCTAssert(decoded.uniqueClients == 13)
120-
XCTAssert(decoded.dnsQueriesAllTypes == 27581)
121-
XCTAssert(decoded.replyNODATA == 2732)
122-
XCTAssert(decoded.replyNXDOMAIN == 1071)
123-
XCTAssert(decoded.replyCNAME == 8179)
124-
XCTAssert(decoded.replyIP == 11865)
125-
XCTAssert(decoded.privacyLevel == 0)
126-
XCTAssert(decoded.status == "enabled")
127-
XCTAssert(decoded.gravityLastUpdated.fileExists == true)
128-
XCTAssert(decoded.gravityLastUpdated.absolute == 1587264260)
129-
XCTAssert(decoded.gravityLastUpdated.relative.days == 0)
130-
XCTAssert(decoded.gravityLastUpdated.relative.hours == 18)
131-
XCTAssert(decoded.gravityLastUpdated.relative.minutes == 47)
132-
110+
XCTAssertEqual(decoded.domainsBeingBlocked, 261271, "it should have 261271 domainsBeingBlocked")
111+
XCTAssertEqual(decoded.dnsQueriesToday, 27581, "it should have 27581 dnsQueriesToday")
112+
XCTAssertEqual(decoded.adsBlockedToday, 4295, "it should have 4295 adsBlockedToday")
113+
XCTAssertEqual(decoded.adsPercentageToday, 15.572314, "it should have 15.572314 adsPercentageToday")
114+
XCTAssertEqual(decoded.uniqueDomains, 2760, "it should have 2760 uniqueDomains")
115+
XCTAssertEqual(decoded.queriesForwarded, 19663, "it should have 19663 queriesForwarded")
116+
XCTAssertEqual(decoded.queriesCached, 3623, "it should have 3623 queriesCached")
117+
XCTAssertEqual(decoded.clientsEverSeen, 14, "it should have 14 clientsEverSeen")
118+
XCTAssertEqual(decoded.uniqueClients, 13, "it should have 13 uniqueClients")
119+
XCTAssertEqual(decoded.dnsQueriesAllTypes, 27581, "it should have 261271 dnsQueriesAllTypes")
120+
XCTAssertEqual(decoded.replyNODATA, 2732, "it should have 261271 replyNODATA")
121+
XCTAssertEqual(decoded.replyNXDOMAIN, 1071, "it should have 1071 replyNXDOMAIN")
122+
XCTAssertEqual(decoded.replyCNAME, 8179, "it should have 8179 replyCNAME")
123+
XCTAssertEqual(decoded.replyIP, 11865, "it should have 11865 replyIP")
124+
XCTAssertEqual(decoded.privacyLevel, 0, "it should have 0 privacyLevel")
125+
XCTAssertEqual(decoded.status, "enabled", "it should have status enabled")
126+
XCTAssertEqual(decoded.gravityLastUpdated.fileExists, true, "it should have fileExists true")
127+
XCTAssertEqual(decoded.gravityLastUpdated.absolute, 1587264260, "it should have 1587264260 gravityLastUpdated.absolute")
128+
XCTAssertEqual(decoded.gravityLastUpdated.relative.days, 0, "it should have 0 gravityLastUpdated.relative.days")
129+
XCTAssertEqual(decoded.gravityLastUpdated.relative.hours, 18, "it should have 18 gravityLastUpdated.relative.hours")
130+
XCTAssertEqual(decoded.gravityLastUpdated.relative.minutes, 47, "it should have 47 gravityLastUpdated.relative.minutes")
131+
} catch {
132+
XCTFail("Can't decode test file bundle \(error)")
133+
}
134+
}
135+
136+
func testDomainsOverTime() {
137+
let jsonString = """
138+
{
139+
"domains_over_time": {
140+
"1593882300": 357,
141+
"1593882900": 209,
142+
"1593883500": 211,
143+
"1593884100": 170,
144+
"1593884700": 274,
145+
"1593885300": 224,
146+
"1593885900": 368,
147+
"1593886500": 238,
148+
"1593887100": 652,
149+
"1593887700": 392,
150+
"1593888300": 331,
151+
"1593888900": 279
152+
},
153+
"ads_over_time": {
154+
"1593882300": 104,
155+
"1593882900": 60,
156+
"1593883500": 34,
157+
"1593884100": 30,
158+
"1593884700": 117,
159+
"1593885300": 71,
160+
"1593885900": 44,
161+
"1593886500": 51,
162+
"1593887100": 83,
163+
"1593887700": 54,
164+
"1593888300": 54,
165+
"1593888900": 70
166+
}
167+
}
168+
"""
169+
170+
guard let data = jsonString.data(using: .utf8) else {
171+
XCTFail("Can't transform string into data")
172+
return
173+
}
174+
175+
let decoder = JSONDecoder()
176+
do {
177+
let decoded = try decoder.decode(HistoricalQueries.self, from: data)
178+
XCTAssertEqual(decoded.requests.count, 12, "it should have 12 requests")
179+
180+
XCTAssertEqual(decoded.requests[0].adsCount, 104, "it should have 104 ads")
181+
XCTAssertEqual(decoded.requests[0].permittedRequests, 253, "it should have 253 permitted requests")
182+
XCTAssertEqual(decoded.requests[0].requestCount, 357, "it should have 253 permitted requests")
183+
XCTAssertEqual(decoded.requests[0].date.timeIntervalSince1970, TimeInterval(1593882300), "date should be 1593882300 timeInterval")
184+
XCTAssertEqual(decoded.requests[0].startDate.timeIntervalSince1970, TimeInterval((1593882000)), "startDate should be 1593882000 timeInterval")
185+
XCTAssertEqual(decoded.requests[0].endDate.timeIntervalSince1970, TimeInterval(1593882599), "startDate should be 1593882000 timeInterval")
186+
187+
XCTAssertEqual(decoded.requests[5].adsCount, 71, "it should have 71 ads")
188+
XCTAssertEqual(decoded.requests[5].permittedRequests, 153, "it should have 153 permitted requests")
189+
XCTAssertEqual(decoded.requests[5].requestCount, 224, "it should have 224 permitted requests")
190+
XCTAssertEqual(decoded.requests[5].date.timeIntervalSince1970, TimeInterval(1593885300), "date should be 1593885300 timeInterval")
191+
XCTAssertEqual(decoded.requests[5].startDate.timeIntervalSince1970, TimeInterval((1593885000)), "startDate should be 1593885000 timeInterval")
192+
XCTAssertEqual(decoded.requests[5].endDate.timeIntervalSince1970, TimeInterval(1593885599), "startDate should be 1593885599 timeInterval")
133193
} catch {
134194
XCTFail("Can't decode test file bundle \(error)")
135195
}

0 commit comments

Comments
 (0)