Add methods for automatically detecting the Pi-Hole
As soon as the user starts up the app, we'll first try to ping their DNS server, as that should in theory be the Pi-Hole. If that doesn't work, then we offer the user the chance to either scan their network or manually enter in the IP address for their Pi.
This commit is contained in:
parent
1ce7b74329
commit
703ca0717a
15 changed files with 446 additions and 209 deletions
90
.gitignore
vendored
Normal file
90
.gitignore
vendored
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
# Xcode
|
||||||
|
#
|
||||||
|
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
|
||||||
|
|
||||||
|
## User settings
|
||||||
|
xcuserdata/
|
||||||
|
|
||||||
|
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
|
||||||
|
*.xcscmblueprint
|
||||||
|
*.xccheckout
|
||||||
|
|
||||||
|
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
|
||||||
|
build/
|
||||||
|
DerivedData/
|
||||||
|
*.moved-aside
|
||||||
|
*.pbxuser
|
||||||
|
!default.pbxuser
|
||||||
|
*.mode1v3
|
||||||
|
!default.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
!default.mode2v3
|
||||||
|
*.perspectivev3
|
||||||
|
!default.perspectivev3
|
||||||
|
|
||||||
|
## Obj-C/Swift specific
|
||||||
|
*.hmap
|
||||||
|
|
||||||
|
## App packaging
|
||||||
|
*.ipa
|
||||||
|
*.dSYM.zip
|
||||||
|
*.dSYM
|
||||||
|
|
||||||
|
## Playgrounds
|
||||||
|
timeline.xctimeline
|
||||||
|
playground.xcworkspace
|
||||||
|
|
||||||
|
# Swift Package Manager
|
||||||
|
#
|
||||||
|
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
|
||||||
|
# Packages/
|
||||||
|
# Package.pins
|
||||||
|
# Package.resolved
|
||||||
|
# *.xcodeproj
|
||||||
|
#
|
||||||
|
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
|
||||||
|
# hence it is not needed unless you have added a package configuration file to your project
|
||||||
|
# .swiftpm
|
||||||
|
|
||||||
|
.build/
|
||||||
|
|
||||||
|
# CocoaPods
|
||||||
|
#
|
||||||
|
# We recommend against adding the Pods directory to your .gitignore. However
|
||||||
|
# you should judge for yourself, the pros and cons are mentioned at:
|
||||||
|
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
||||||
|
#
|
||||||
|
# Pods/
|
||||||
|
#
|
||||||
|
# Add this line if you want to avoid checking in source code from the Xcode workspace
|
||||||
|
# *.xcworkspace
|
||||||
|
|
||||||
|
# Carthage
|
||||||
|
#
|
||||||
|
# Add this line if you want to avoid checking in source code from Carthage dependencies.
|
||||||
|
# Carthage/Checkouts
|
||||||
|
|
||||||
|
Carthage/Build/
|
||||||
|
|
||||||
|
# Accio dependency management
|
||||||
|
Dependencies/
|
||||||
|
.accio/
|
||||||
|
|
||||||
|
# fastlane
|
||||||
|
#
|
||||||
|
# It is recommended to not store the screenshots in the git repo.
|
||||||
|
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
|
||||||
|
# For more information about the recommended setup visit:
|
||||||
|
# https://docs.fastlane.tools/best-practices/source-control/#source-control
|
||||||
|
|
||||||
|
fastlane/report.xml
|
||||||
|
fastlane/Preview.html
|
||||||
|
fastlane/screenshots/**/*.png
|
||||||
|
fastlane/test_output
|
||||||
|
|
||||||
|
# Code Injection
|
||||||
|
#
|
||||||
|
# After new code Injection tools there's a generated folder /iOSInjectionProject
|
||||||
|
# https://github.com/johnno1962/injectionforxcode
|
||||||
|
|
||||||
|
iOSInjectionProject/
|
|
@ -22,6 +22,9 @@
|
||||||
282126AE235BF21D00072D52 /* AddPiHoleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 282126AD235BF21D00072D52 /* AddPiHoleView.swift */; };
|
282126AE235BF21D00072D52 /* AddPiHoleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 282126AD235BF21D00072D52 /* AddPiHoleView.swift */; };
|
||||||
282126B0235BF52F00072D52 /* ActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 282126AF235BF52F00072D52 /* ActivityIndicatorView.swift */; };
|
282126B0235BF52F00072D52 /* ActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 282126AF235BF52F00072D52 /* ActivityIndicatorView.swift */; };
|
||||||
282126B4235C0F5400072D52 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 282126B6235C0F5400072D52 /* Localizable.strings */; };
|
282126B4235C0F5400072D52 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 282126B6235C0F5400072D52 /* Localizable.strings */; };
|
||||||
|
28D72051238067F30038D439 /* libresolv.9.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 28D72050238067EC0038D439 /* libresolv.9.tbd */; };
|
||||||
|
28D7205923808E8C0038D439 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D7205823808E8C0038D439 /* Extensions.swift */; };
|
||||||
|
28D7205C2380C2090038D439 /* resolver.c in Sources */ = {isa = PBXBuildFile; fileRef = 28D7205B2380C2090038D439 /* resolver.c */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
@ -65,6 +68,12 @@
|
||||||
282126B3235C0EBF00072D52 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/LaunchScreen.strings"; sourceTree = "<group>"; };
|
282126B3235C0EBF00072D52 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/LaunchScreen.strings"; sourceTree = "<group>"; };
|
||||||
282126B5235C0F5400072D52 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
282126B5235C0F5400072D52 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
282126B8235C0F8400072D52 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
282126B8235C0F8400072D52 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||||
|
28D7204E238067B20038D439 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
|
||||||
|
28D72050238067EC0038D439 /* libresolv.9.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.9.tbd; path = usr/lib/libresolv.9.tbd; sourceTree = SDKROOT; };
|
||||||
|
28D720522380689E0038D439 /* Pi-Helper-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Pi-Helper-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
28D7205823808E8C0038D439 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
|
||||||
|
28D7205A2380C2090038D439 /* resolver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = resolver.h; sourceTree = "<group>"; };
|
||||||
|
28D7205B2380C2090038D439 /* resolver.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = resolver.c; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
@ -72,6 +81,7 @@
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
28D72051238067F30038D439 /* libresolv.9.tbd in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -99,6 +109,7 @@
|
||||||
28212685235926EB00072D52 /* Pi-HelperTests */,
|
28212685235926EB00072D52 /* Pi-HelperTests */,
|
||||||
28212690235926EB00072D52 /* Pi-HelperUITests */,
|
28212690235926EB00072D52 /* Pi-HelperUITests */,
|
||||||
2821266D235926E700072D52 /* Products */,
|
2821266D235926E700072D52 /* Products */,
|
||||||
|
28D7204D238067B20038D439 /* Frameworks */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
@ -115,6 +126,7 @@
|
||||||
2821266E235926E700072D52 /* Pi-Helper */ = {
|
2821266E235926E700072D52 /* Pi-Helper */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
28D720522380689E0038D439 /* Pi-Helper-Bridging-Header.h */,
|
||||||
2821266F235926E700072D52 /* AppDelegate.swift */,
|
2821266F235926E700072D52 /* AppDelegate.swift */,
|
||||||
28212671235926E700072D52 /* SceneDelegate.swift */,
|
28212671235926E700072D52 /* SceneDelegate.swift */,
|
||||||
28212673235926E700072D52 /* ContentView.swift */,
|
28212673235926E700072D52 /* ContentView.swift */,
|
||||||
|
@ -129,6 +141,9 @@
|
||||||
282126AB235BF09800072D52 /* PiHoleDetailsView.swift */,
|
282126AB235BF09800072D52 /* PiHoleDetailsView.swift */,
|
||||||
282126AF235BF52F00072D52 /* ActivityIndicatorView.swift */,
|
282126AF235BF52F00072D52 /* ActivityIndicatorView.swift */,
|
||||||
282126B6235C0F5400072D52 /* Localizable.strings */,
|
282126B6235C0F5400072D52 /* Localizable.strings */,
|
||||||
|
28D7205823808E8C0038D439 /* Extensions.swift */,
|
||||||
|
28D7205A2380C2090038D439 /* resolver.h */,
|
||||||
|
28D7205B2380C2090038D439 /* resolver.c */,
|
||||||
);
|
);
|
||||||
path = "Pi-Helper";
|
path = "Pi-Helper";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -159,6 +174,15 @@
|
||||||
path = "Pi-HelperUITests";
|
path = "Pi-HelperUITests";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
28D7204D238067B20038D439 /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
28D72050238067EC0038D439 /* libresolv.9.tbd */,
|
||||||
|
28D7204E238067B20038D439 /* libresolv.tbd */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
|
@ -227,6 +251,7 @@
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
2821266B235926E700072D52 = {
|
2821266B235926E700072D52 = {
|
||||||
CreatedOnToolsVersion = 11.0;
|
CreatedOnToolsVersion = 11.0;
|
||||||
|
LastSwiftMigration = 1120;
|
||||||
};
|
};
|
||||||
28212681235926EB00072D52 = {
|
28212681235926EB00072D52 = {
|
||||||
CreatedOnToolsVersion = 11.0;
|
CreatedOnToolsVersion = 11.0;
|
||||||
|
@ -297,9 +322,11 @@
|
||||||
282126AA235BEE0F00072D52 /* PiHoleApiService.swift in Sources */,
|
282126AA235BEE0F00072D52 /* PiHoleApiService.swift in Sources */,
|
||||||
282126AE235BF21D00072D52 /* AddPiHoleView.swift in Sources */,
|
282126AE235BF21D00072D52 /* AddPiHoleView.swift in Sources */,
|
||||||
28212670235926E700072D52 /* AppDelegate.swift in Sources */,
|
28212670235926E700072D52 /* AppDelegate.swift in Sources */,
|
||||||
|
28D7205C2380C2090038D439 /* resolver.c in Sources */,
|
||||||
28212672235926E700072D52 /* SceneDelegate.swift in Sources */,
|
28212672235926E700072D52 /* SceneDelegate.swift in Sources */,
|
||||||
282126B0235BF52F00072D52 /* ActivityIndicatorView.swift in Sources */,
|
282126B0235BF52F00072D52 /* ActivityIndicatorView.swift in Sources */,
|
||||||
28212674235926E700072D52 /* ContentView.swift in Sources */,
|
28212674235926E700072D52 /* ContentView.swift in Sources */,
|
||||||
|
28D7205923808E8C0038D439 /* Extensions.swift in Sources */,
|
||||||
282126A8235BE72800072D52 /* PiHole.swift in Sources */,
|
282126A8235BE72800072D52 /* PiHole.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -477,6 +504,7 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Pi-Helper/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Pi-Helper/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = 9Z6DE6KNJ9;
|
DEVELOPMENT_TEAM = 9Z6DE6KNJ9;
|
||||||
|
@ -488,6 +516,8 @@
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.wbrawner.Pi-Helper";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.wbrawner.Pi-Helper";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Pi-Helper/Pi-Helper-Bridging-Header.h";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
|
@ -497,6 +527,7 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Pi-Helper/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Pi-Helper/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = 9Z6DE6KNJ9;
|
DEVELOPMENT_TEAM = 9Z6DE6KNJ9;
|
||||||
|
@ -508,6 +539,7 @@
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.wbrawner.Pi-Helper";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.wbrawner.Pi-Helper";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Pi-Helper/Pi-Helper-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,168 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Bucket
|
|
||||||
uuid = "59EB2C60-D493-44EF-8FB1-43FCFB54B3E8"
|
|
||||||
type = "1"
|
|
||||||
version = "2.0">
|
|
||||||
<Breakpoints>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "794E3259-7833-47A1-B889-ACE3E7B7B788"
|
|
||||||
shouldBeEnabled = "No"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "Pi-Helper/PiHoleDataStore.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "21"
|
|
||||||
endingLineNumber = "21"
|
|
||||||
landmarkName = "loadSummary(_:apiKey:)"
|
|
||||||
landmarkType = "7">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "70FF2209-0733-48C4-B198-1F58A9D129D8"
|
|
||||||
shouldBeEnabled = "No"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "Pi-Helper/PiHoleDataStore.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "33"
|
|
||||||
endingLineNumber = "33"
|
|
||||||
landmarkName = "loadSummary(_:apiKey:)"
|
|
||||||
landmarkType = "7">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "61C30D69-66FB-4844-8C00-1A8C239EB9E9"
|
|
||||||
shouldBeEnabled = "No"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "Pi-Helper/PiHoleDataStore.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "36"
|
|
||||||
endingLineNumber = "36"
|
|
||||||
landmarkName = "loadSummary(_:apiKey:)"
|
|
||||||
landmarkType = "7">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "1206CF28-BA55-4380-8D4D-6806755092E4"
|
|
||||||
shouldBeEnabled = "No"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "Pi-Helper/ContentView.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "19"
|
|
||||||
endingLineNumber = "19"
|
|
||||||
landmarkName = "stateContent"
|
|
||||||
landmarkType = "24">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "19509035-C3D0-4116-8A99-AAFB00F61136"
|
|
||||||
shouldBeEnabled = "No"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "Pi-Helper/ContentView.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "21"
|
|
||||||
endingLineNumber = "21"
|
|
||||||
landmarkName = "stateContent"
|
|
||||||
landmarkType = "24">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "B75FD518-9E34-4579-89C6-27498913E3DC"
|
|
||||||
shouldBeEnabled = "No"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "Pi-Helper/ContentView.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "23"
|
|
||||||
endingLineNumber = "23"
|
|
||||||
landmarkName = "stateContent"
|
|
||||||
landmarkType = "24">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "F5CC5BA4-EE2D-45D1-8487-3D9C78C6D735"
|
|
||||||
shouldBeEnabled = "No"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "Pi-Helper/PiHoleDataStore.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "104"
|
|
||||||
endingLineNumber = "104"
|
|
||||||
landmarkName = "disable(_:)"
|
|
||||||
landmarkType = "7">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "6C7C0D86-0A5F-403F-A1B6-36AB78805BD1"
|
|
||||||
shouldBeEnabled = "No"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "Pi-Helper/PiHoleDataStore.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "101"
|
|
||||||
endingLineNumber = "101"
|
|
||||||
landmarkName = "disable(_:)"
|
|
||||||
landmarkType = "7">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "A60B4D50-A0F1-4565-AB8B-B40648E7A622"
|
|
||||||
shouldBeEnabled = "No"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "Pi-Helper/PiHoleApiService.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "64"
|
|
||||||
endingLineNumber = "64"
|
|
||||||
landmarkName = "get(_:queries:)"
|
|
||||||
landmarkType = "7">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "918A86D9-9B8C-4C31-8536-B20357C2E017"
|
|
||||||
shouldBeEnabled = "Yes"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "Pi-Helper/SceneDelegate.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "79"
|
|
||||||
endingLineNumber = "79"
|
|
||||||
landmarkName = "windowScene(_:performActionFor:completionHandler:)"
|
|
||||||
landmarkType = "7">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
</Breakpoints>
|
|
||||||
</Bucket>
|
|
|
@ -9,17 +9,22 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct AddPiHoleView: View {
|
struct AddPiHoleView: View {
|
||||||
@State var ipAddress: String = ""
|
var statefulContent: AnyView {
|
||||||
@State var apiKey: String = ""
|
switch self.dataStore.pihole {
|
||||||
|
case .failure(let piholeError):
|
||||||
|
switch piholeError {
|
||||||
|
case .scanning(let ipAddress):
|
||||||
|
return ScanningView(dataStore: self.dataStore, ipAddress: ipAddress).toAnyView()
|
||||||
|
default:
|
||||||
|
return ManuallyAddPiHoleView(dataStore: self.dataStore).toAnyView()
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return ManuallyAddPiHoleView(dataStore: self.dataStore).toAnyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
statefulContent
|
||||||
Text("add_pihole")
|
|
||||||
TextField("ip_address", text: $ipAddress)
|
|
||||||
SecureField("api_key_optional", text: $apiKey)
|
|
||||||
Button(action: { self.dataStore.loadSummary(self.ipAddress, apiKey: self.apiKey) }, label: { Text("connect") })
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ObservedObject var dataStore: PiHoleDataStore
|
@ObservedObject var dataStore: PiHoleDataStore
|
||||||
|
@ -28,6 +33,62 @@ struct AddPiHoleView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ManuallyAddPiHoleView: View {
|
||||||
|
@State var ipAddress: String = ""
|
||||||
|
@State var apiKey: String = ""
|
||||||
|
@ObservedObject var dataStore: PiHoleDataStore
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
Text("add_pihole")
|
||||||
|
TextField("ip_address", text: $ipAddress)
|
||||||
|
SecureField("api_key_optional", text: $apiKey)
|
||||||
|
Button(action: {
|
||||||
|
self.dataStore.baseUrl = self.ipAddress
|
||||||
|
self.dataStore.apiKey = self.apiKey
|
||||||
|
self.dataStore.loadSummary()
|
||||||
|
}, label: { Text("connect") })
|
||||||
|
.padding()
|
||||||
|
ScanButton(dataStore: self.dataStore)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ScanButton: View {
|
||||||
|
@ObservedObject var dataStore: PiHoleDataStore
|
||||||
|
|
||||||
|
var statefulContent: AnyView {
|
||||||
|
if let deviceIpAddress = resolver_get_device_ip() {
|
||||||
|
return Button(action: {
|
||||||
|
self.dataStore.beginScanning(String(cString: deviceIpAddress))
|
||||||
|
}, label: { Text("scan") }).padding().toAnyView()
|
||||||
|
} else {
|
||||||
|
return EmptyView().toAnyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View { statefulContent }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ScanningView: View {
|
||||||
|
let dataStore: PiHoleDataStore
|
||||||
|
let ipAddress: String
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
ActivityIndicatorView(.constant(true))
|
||||||
|
Text("scanning_ip_address")
|
||||||
|
Text(verbatim: ipAddress)
|
||||||
|
Button(action: {
|
||||||
|
self.dataStore.cancelRequest()
|
||||||
|
}, label: { Text("cancel") }
|
||||||
|
)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct AddPiHoleView_Previews: PreviewProvider {
|
struct AddPiHoleView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
AddPiHoleView(PiHoleDataStore())
|
AddPiHoleView(PiHoleDataStore())
|
||||||
|
|
|
@ -16,11 +16,11 @@ struct ContentView: View {
|
||||||
var stateContent: AnyView {
|
var stateContent: AnyView {
|
||||||
switch self.dataStore.pihole {
|
switch self.dataStore.pihole {
|
||||||
case .success(_):
|
case .success(_):
|
||||||
return AnyView(PiHoleDetailsView(self.dataStore))
|
return PiHoleDetailsView(self.dataStore).toAnyView()
|
||||||
case .failure(.loading):
|
case .failure(.networkError(.loading)):
|
||||||
return AnyView(ActivityIndicatorView(.constant(true)))
|
return ActivityIndicatorView(.constant(true)).toAnyView()
|
||||||
default:
|
default:
|
||||||
return AnyView(AddPiHoleView(self.dataStore))
|
return AddPiHoleView(self.dataStore).toAnyView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
16
Pi-Helper/Extensions.swift
Normal file
16
Pi-Helper/Extensions.swift
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
//
|
||||||
|
// Extensions.swift
|
||||||
|
// Pi-Helper
|
||||||
|
//
|
||||||
|
// Created by Billy Brawner on 11/16/19.
|
||||||
|
// Copyright © 2019 William Brawner. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
func toAnyView() -> AnyView {
|
||||||
|
return AnyView(self)
|
||||||
|
}
|
||||||
|
}
|
5
Pi-Helper/Pi-Helper-Bridging-Header.h
Normal file
5
Pi-Helper/Pi-Helper-Bridging-Header.h
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
//
|
||||||
|
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "resolver.h"
|
|
@ -110,6 +110,10 @@ struct StatusUpdate: Codable {
|
||||||
let status: String
|
let status: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct VersionResponse: Codable {
|
||||||
|
let version: Int
|
||||||
|
}
|
||||||
|
|
||||||
enum PiHoleStatus {
|
enum PiHoleStatus {
|
||||||
case enabled
|
case enabled
|
||||||
case disabled
|
case disabled
|
||||||
|
|
|
@ -20,6 +20,10 @@ class PiHoleApiService {
|
||||||
self.decoder = decoder
|
self.decoder = decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getVersion() -> AnyPublisher<VersionResponse, NetworkError> {
|
||||||
|
return get(queries: ["version": nil])
|
||||||
|
}
|
||||||
|
|
||||||
func loadSummary() -> AnyPublisher<PiHole, NetworkError> {
|
func loadSummary() -> AnyPublisher<PiHole, NetworkError> {
|
||||||
return get()
|
return get()
|
||||||
}
|
}
|
||||||
|
@ -68,6 +72,7 @@ class PiHoleApiService {
|
||||||
var request = URLRequest(url: url)
|
var request = URLRequest(url: url)
|
||||||
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||||
request.httpMethod = "GET"
|
request.httpMethod = "GET"
|
||||||
|
request.timeoutInterval = 0.5
|
||||||
|
|
||||||
let task = URLSession.shared.dataTaskPublisher(for: request)
|
let task = URLSession.shared.dataTaskPublisher(for: request)
|
||||||
.tryMap { (data, res) -> Data in
|
.tryMap { (data, res) -> Data in
|
||||||
|
@ -91,6 +96,7 @@ class PiHoleApiService {
|
||||||
|
|
||||||
enum NetworkError: Error {
|
enum NetworkError: Error {
|
||||||
case loading
|
case loading
|
||||||
|
case cancelled
|
||||||
case badRequest
|
case badRequest
|
||||||
case notFound
|
case notFound
|
||||||
case unauthorized
|
case unauthorized
|
||||||
|
|
|
@ -11,22 +11,95 @@ import Combine
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class PiHoleDataStore: ObservableObject {
|
class PiHoleDataStore: ObservableObject {
|
||||||
var pihole: Result<PiHole, NetworkError> = .failure(.notFound) {
|
private let IP_MIN = 0
|
||||||
|
private let IP_MAX = 255
|
||||||
|
private var currentRequest: AnyCancellable? = nil
|
||||||
|
@Published var pihole: Result<PiHole, PiHoleError> = .failure(.notConfigured)
|
||||||
|
var apiKey: String? = nil {
|
||||||
didSet {
|
didSet {
|
||||||
self.objectWillChange.send()
|
UserDefaults.standard.set(apiKey, forKey: PiHoleDataStore.API_KEY)
|
||||||
|
apiService.apiKey = apiKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var baseUrl: String? = nil {
|
||||||
|
didSet {
|
||||||
|
let safeHost = prependScheme(baseUrl)
|
||||||
|
UserDefaults.standard.set(safeHost, forKey: PiHoleDataStore.HOST_KEY)
|
||||||
|
apiService.baseUrl = safeHost
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadSummary(_ host: String, apiKey: String? = nil) {
|
private func prependScheme(_ ipAddress: String?) -> String? {
|
||||||
self.pihole = .failure(.loading)
|
guard let host = ipAddress else {
|
||||||
|
return nil
|
||||||
var safeHost = host
|
|
||||||
if !host.starts(with: "http://") || !host.starts(with: "https://") {
|
|
||||||
safeHost = "http://" + safeHost
|
|
||||||
}
|
}
|
||||||
apiService.baseUrl = safeHost
|
|
||||||
apiService.apiKey = apiKey
|
if !host.starts(with: "http://") && !host.starts(with: "https://") {
|
||||||
_ = apiService.loadSummary()
|
return "http://" + host
|
||||||
|
}
|
||||||
|
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
|
||||||
|
func beginScanning(_ ipAddress: String) {
|
||||||
|
var addressParts = ipAddress.split(separator: ".")
|
||||||
|
var chunks = 1
|
||||||
|
var ipAddresses = [String]()
|
||||||
|
while chunks <= IP_MAX {
|
||||||
|
let chunkSize = (IP_MAX - IP_MIN + 1) / chunks
|
||||||
|
if chunkSize == 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for chunk in 0..<chunks {
|
||||||
|
let chunkStart = IP_MIN + (chunk * chunkSize)
|
||||||
|
let chunkEnd = IP_MIN + ((chunk + 1) * chunkSize)
|
||||||
|
addressParts[3] = Substring(String(((chunkEnd - chunkStart) / 2) + chunkStart))
|
||||||
|
ipAddresses.append(addressParts.joined(separator: "."))
|
||||||
|
}
|
||||||
|
chunks *= 2
|
||||||
|
}
|
||||||
|
scan(ipAddresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func scan(_ ipAddresses: [String]) {
|
||||||
|
if ipAddresses.isEmpty {
|
||||||
|
self.pihole = .failure(.notConfigured)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let ipAddress = prependScheme(ipAddresses[0]) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.apiService.baseUrl = ipAddress
|
||||||
|
self.pihole = .failure(.scanning(ipAddress))
|
||||||
|
print("Scanning \(ipAddress)")
|
||||||
|
currentRequest = self.apiService.getVersion()
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink(receiveCompletion: { (completion) in
|
||||||
|
switch completion {
|
||||||
|
case .finished:
|
||||||
|
return
|
||||||
|
case .failure(let error):
|
||||||
|
// ignore if timeout, otherwise handle
|
||||||
|
print(error)
|
||||||
|
self.scan(Array(ipAddresses.dropFirst()))
|
||||||
|
}
|
||||||
|
}, receiveValue: { version in
|
||||||
|
// Stop scans, load summary
|
||||||
|
self.baseUrl = ipAddress
|
||||||
|
self.loadSummary()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancelRequest() {
|
||||||
|
self.currentRequest?.cancel()
|
||||||
|
self.pihole = .failure(.networkError(.cancelled))
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadSummary() {
|
||||||
|
self.pihole = .failure(.networkError(.loading))
|
||||||
|
|
||||||
|
currentRequest = apiService.loadSummary()
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink(receiveCompletion: { (completion) in
|
.sink(receiveCompletion: { (completion) in
|
||||||
switch completion {
|
switch completion {
|
||||||
|
@ -34,11 +107,9 @@ class PiHoleDataStore: ObservableObject {
|
||||||
// no-op
|
// no-op
|
||||||
return
|
return
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
self.pihole = .failure(error)
|
self.pihole = .failure(.networkError(error))
|
||||||
}
|
}
|
||||||
}, receiveValue: { pihole in
|
}, receiveValue: { pihole in
|
||||||
UserDefaults.standard.set(host, forKey: PiHoleDataStore.HOST_KEY)
|
|
||||||
UserDefaults.standard.set(apiKey, forKey: PiHoleDataStore.API_KEY)
|
|
||||||
UIApplication.shared.shortcutItems = [
|
UIApplication.shared.shortcutItems = [
|
||||||
UIApplicationShortcutItem(
|
UIApplicationShortcutItem(
|
||||||
type: ShortcutAction.enable.rawValue,
|
type: ShortcutAction.enable.rawValue,
|
||||||
|
@ -75,8 +146,8 @@ class PiHoleDataStore: ObservableObject {
|
||||||
|
|
||||||
func enable() {
|
func enable() {
|
||||||
let oldPihole = try! pihole.get()
|
let oldPihole = try! pihole.get()
|
||||||
self.pihole = .failure(.loading)
|
self.pihole = .failure(.networkError(.loading))
|
||||||
_ = self.apiService.enable()
|
currentRequest = self.apiService.enable()
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink(receiveCompletion: { (completion) in
|
.sink(receiveCompletion: { (completion) in
|
||||||
switch completion {
|
switch completion {
|
||||||
|
@ -84,7 +155,7 @@ class PiHoleDataStore: ObservableObject {
|
||||||
// no-op
|
// no-op
|
||||||
return
|
return
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
self.pihole = .failure(error)
|
self.pihole = .failure(.networkError(error))
|
||||||
}
|
}
|
||||||
}, receiveValue: { newStatus in
|
}, receiveValue: { newStatus in
|
||||||
self.pihole = .success(oldPihole.copy(status: newStatus.status))
|
self.pihole = .success(oldPihole.copy(status: newStatus.status))
|
||||||
|
@ -93,8 +164,8 @@ class PiHoleDataStore: ObservableObject {
|
||||||
|
|
||||||
func disable(_ forSeconds: Int? = nil) {
|
func disable(_ forSeconds: Int? = nil) {
|
||||||
let oldPihole = try! pihole.get()
|
let oldPihole = try! pihole.get()
|
||||||
self.pihole = .failure(.loading)
|
self.pihole = .failure(.networkError(.loading))
|
||||||
_ = self.apiService.disable(forSeconds)
|
currentRequest = self.apiService.disable(forSeconds)
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink(receiveCompletion: { (completion) in
|
.sink(receiveCompletion: { (completion) in
|
||||||
switch completion {
|
switch completion {
|
||||||
|
@ -102,26 +173,25 @@ class PiHoleDataStore: ObservableObject {
|
||||||
// no-op
|
// no-op
|
||||||
return
|
return
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
self.pihole = .failure(error)
|
self.pihole = .failure(.networkError(error))
|
||||||
}
|
}
|
||||||
}, receiveValue: { newStatus in
|
}, receiveValue: { newStatus in
|
||||||
self.pihole = .success(oldPihole.copy(status: newStatus.status))
|
self.pihole = .success(oldPihole.copy(status: newStatus.status))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let objectWillChange = ObservableObjectPublisher()
|
|
||||||
let apiService = PiHoleApiService()
|
let apiService = PiHoleApiService()
|
||||||
static let HOST_KEY = "host"
|
static let HOST_KEY = "host"
|
||||||
static let API_KEY = "apiKey"
|
static let API_KEY = "apiKey"
|
||||||
init() {
|
|
||||||
if let host = UserDefaults.standard.string(forKey: PiHoleDataStore.HOST_KEY) {
|
|
||||||
let apiKey = UserDefaults.standard.string(forKey: PiHoleDataStore.API_KEY)
|
|
||||||
loadSummary(host, apiKey: apiKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ShortcutAction: String {
|
enum ShortcutAction: String {
|
||||||
case enable = "EnableAction"
|
case enable = "EnableAction"
|
||||||
case disable = "DisableAction"
|
case disable = "DisableAction"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum PiHoleError : Error {
|
||||||
|
case networkError(_ error: NetworkError)
|
||||||
|
case scanning(_ ipAddress: String)
|
||||||
|
case notConfigured
|
||||||
|
}
|
||||||
|
|
|
@ -18,8 +18,20 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
|
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
|
||||||
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
|
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
|
||||||
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
|
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
|
||||||
|
|
||||||
self.dataStore = PiHoleDataStore()
|
self.dataStore = PiHoleDataStore()
|
||||||
|
if let host = UserDefaults.standard.string(forKey: PiHoleDataStore.HOST_KEY) {
|
||||||
|
// If we already have the address of a Pi we've previously connected to, try that
|
||||||
|
let apiKey = UserDefaults.standard.string(forKey: PiHoleDataStore.API_KEY)
|
||||||
|
self.dataStore!.baseUrl = host
|
||||||
|
self.dataStore!.apiKey = apiKey
|
||||||
|
self.dataStore!.loadSummary()
|
||||||
|
} else if let cString = resolver_get_dns_server_ip() {
|
||||||
|
// Otherwise the Pi is likely the DNS server (though not always true), so we can try connecting to it there
|
||||||
|
let dnsServerIp = String(cString: cString)
|
||||||
|
self.dataStore!.baseUrl = dnsServerIp
|
||||||
|
self.dataStore!.loadSummary()
|
||||||
|
}
|
||||||
|
|
||||||
// Create the SwiftUI view that provides the window contents.
|
// Create the SwiftUI view that provides the window contents.
|
||||||
let contentView = ContentView(self.dataStore!)
|
let contentView = ContentView(self.dataStore!)
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
"add_pihole" = "Add the IP address or hostname of your Pi-Hole to be able to view statistics. To enable or disable the Pi-Hole, you'll also need to enter the API key.";
|
"add_pihole" = "Add the IP address or hostname of your Pi-Hole to be able to view statistics. To enable or disable the Pi-Hole, you'll also need to enter the API key.";
|
||||||
"ip_address" = "IP Address";
|
"ip_address" = "IP Address";
|
||||||
"api_key_optional" = "API Key (Optional)";
|
"api_key_optional" = "API Key (Optional)";
|
||||||
|
"scanning_ip_address" = "Scanning the network for your Pi-Hole…";
|
||||||
|
"scan" = "Scan";
|
||||||
|
"cancel" = "Cancel";
|
||||||
"connect" = "Connect";
|
"connect" = "Connect";
|
||||||
"status" = "Status";
|
"status" = "Status";
|
||||||
"enabled" = "Enabled";
|
"enabled" = "Enabled";
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
"add_pihole" = "Agrega la dirección IP o nombre de host de tu Pi-Hole para poder ver las estadísticas. Para habilitar o deshabilitar el Pi-Hole, también es necesario entregar la clave API.";
|
"add_pihole" = "Agrega la dirección IP o nombre de host de tu Pi-Hole para poder ver las estadísticas. Para habilitar o deshabilitar el Pi-Hole, también es necesario entregar la clave API.";
|
||||||
"ip_address" = "Dirección IP";
|
"ip_address" = "Dirección IP";
|
||||||
"api_key_optional" = "Clave API (Opcional)";
|
"api_key_optional" = "Clave API (Opcional)";
|
||||||
|
"scanning_ip_address" = "Escaneando la red para el Pi-Hole…";
|
||||||
|
"scan" = "Escanear";
|
||||||
|
"cancel" = "Cancelar";
|
||||||
"connect" = "Conectar";
|
"connect" = "Conectar";
|
||||||
"status" = "Estado";
|
"status" = "Estado";
|
||||||
"enabled" = "Habilitado";
|
"enabled" = "Habilitado";
|
||||||
|
|
77
Pi-Helper/resolver.c
Normal file
77
Pi-Helper/resolver.c
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
//
|
||||||
|
// resolver.c
|
||||||
|
// Pi-Helper
|
||||||
|
//
|
||||||
|
// Created by Billy Brawner on 11/16/19.
|
||||||
|
// Copyright © 2019 William Brawner. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "resolver.h"
|
||||||
|
|
||||||
|
char * resolver_get_dns_server_ip(void) {
|
||||||
|
res_state state = malloc(sizeof(res_state));
|
||||||
|
res_ninit(state);
|
||||||
|
union res_sockaddr_union sockaddr_unions[MAX_SERVERS];
|
||||||
|
int servers_found = res_getservers(state, sockaddr_unions, MAX_SERVERS);
|
||||||
|
res_ndestroy(state);
|
||||||
|
for (int i = 0; i < servers_found; i++) {
|
||||||
|
union res_sockaddr_union sockaddr_union = sockaddr_unions[i];
|
||||||
|
if (sockaddr_union.sin.sin_len < 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
char * ipAddress = malloc(NI_MAXHOST + 1);
|
||||||
|
ipAddress[NI_MAXHOST] = '\0';
|
||||||
|
getnameinfo(
|
||||||
|
&sockaddr_union.sin,
|
||||||
|
sockaddr_union.sin.sin_len,
|
||||||
|
ipAddress,
|
||||||
|
MAX_IP_ADDR_LEN,
|
||||||
|
NULL,
|
||||||
|
0,
|
||||||
|
NI_NUMERICHOST
|
||||||
|
);
|
||||||
|
return ipAddress;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char * resolver_get_device_ip(void) {
|
||||||
|
struct ifaddrs *ifaddr, *ifa;
|
||||||
|
if (getifaddrs(&ifaddr) == -1) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int n;
|
||||||
|
for (ifa = ifaddr, n = 0; ifa != NULL; ifa = ifa->ifa_next, n++) {
|
||||||
|
if (ifa->ifa_addr == NULL) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ifa->ifa_addr->sa_family != AF_INET) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp("en0", ifa->ifa_name) == 0
|
||||||
|
|| strcmp("en1", ifa->ifa_name) == 0
|
||||||
|
|| strcmp("en2", ifa->ifa_name) == 0
|
||||||
|
|| strcmp("en3", ifa->ifa_name) == 0
|
||||||
|
|| strcmp("en4", ifa->ifa_name) == 0) {
|
||||||
|
char *ipAddress = malloc(NI_MAXHOST + 1);
|
||||||
|
ipAddress[NI_NUMERICHOST] = '\0';
|
||||||
|
getnameinfo(
|
||||||
|
ifa->ifa_addr,
|
||||||
|
ifa->ifa_addr->sa_len,
|
||||||
|
ipAddress,
|
||||||
|
NI_MAXHOST,
|
||||||
|
NULL,
|
||||||
|
0,
|
||||||
|
NI_NUMERICHOST
|
||||||
|
);
|
||||||
|
freeifaddrs(ifaddr);
|
||||||
|
return ipAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
freeifaddrs(ifaddr);
|
||||||
|
return NULL;
|
||||||
|
}
|
26
Pi-Helper/resolver.h
Normal file
26
Pi-Helper/resolver.h
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
//
|
||||||
|
// resolver.h
|
||||||
|
// Pi-Helper
|
||||||
|
//
|
||||||
|
// Created by Billy Brawner on 11/16/19.
|
||||||
|
// Copyright © 2019 William Brawner. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef resolver_h
|
||||||
|
#define resolver_h
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <resolv.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <ifaddrs.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
const int MAX_SERVERS = 10;
|
||||||
|
// String length for max IP address length (255.255.255.255)
|
||||||
|
const int MAX_IP_ADDR_LEN = 15;
|
||||||
|
|
||||||
|
char * resolver_get_dns_server_ip(void);
|
||||||
|
char * resolver_get_device_ip(void);
|
||||||
|
|
||||||
|
#endif /* resolver_h */
|
Loading…
Reference in a new issue