Compare commits
90 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e54d69ef4b | ||
|
|
be8269ad56 | ||
|
|
7118ec3b02 | ||
|
|
71045bf0dd | ||
|
|
27abdd66f5 | ||
|
|
162e18c912 | ||
|
|
d68e4ec869 | ||
|
|
762263bfbd | ||
|
|
b1cddc796e | ||
|
|
77e20f31f5 | ||
|
|
0175f5390e | ||
|
|
effc305b86 | ||
|
|
c1fe258b0d | ||
|
|
36a8f0b97b | ||
|
|
33b9cab8a8 | ||
|
|
b88874b38b | ||
|
|
f55f3ea32d | ||
|
|
c843bd76a2 | ||
|
|
4dd2339ed8 | ||
|
|
280526bef4 | ||
|
|
34caffd4a7 | ||
|
|
9e19b457e2 | ||
|
|
e6846953b7 | ||
|
|
6d78aeac7b | ||
|
|
5d94fe3a0d | ||
|
|
fb680d669b | ||
|
|
6409e5eaf3 | ||
|
|
39ca9dbdb1 | ||
|
|
27ab2a621a | ||
|
|
3f572eeb15 | ||
|
|
e83540d5de | ||
|
|
847556bec1 | ||
|
|
42b045fb85 | ||
|
|
35a211f87f | ||
|
|
d2fa67e0e3 | ||
|
|
b8660c9a35 | ||
|
|
8cd3f7fb3a | ||
|
|
2ee0272a05 | ||
|
|
4ae82fc763 | ||
|
|
aac42d7eff | ||
|
|
8bb77ef741 | ||
|
|
ff4218981f | ||
|
|
7b7c5f3d9a | ||
|
|
1c203e39c3 | ||
|
|
7dbf21d564 | ||
|
|
8fcb5ad874 | ||
|
|
b4bf705b7f | ||
|
|
69d8321180 | ||
|
|
b03daeca66 | ||
|
|
c502484bcf | ||
|
|
448d69c6d8 | ||
|
|
42aa7cf926 | ||
|
|
52fa2e460e | ||
|
|
8855ae754a | ||
|
|
908a909c87 | ||
|
|
41aee797a9 | ||
|
|
685f636d5b | ||
|
|
4af56b0cb1 | ||
|
|
a3973c7e9a | ||
|
|
b270f30f3c | ||
|
|
03177cee0b | ||
|
|
9ee094dc20 | ||
|
|
b1d49c6765 | ||
|
|
b774e2152c | ||
|
|
e398ac8bcd | ||
|
|
01523b250f | ||
|
|
a2b0f311d5 | ||
|
|
88a52fb92c | ||
|
|
723f1665a7 | ||
|
|
4f92d3d58d | ||
|
|
05d06a4f31 | ||
|
|
f9ab545e0f | ||
|
|
b10d4c8b36 | ||
|
|
5a3ca024f8 | ||
|
|
92216c0c03 | ||
|
|
9ece3474c6 | ||
|
|
6dcc2086e6 | ||
|
|
08483711e2 | ||
|
|
0e100006d3 | ||
|
|
710c617862 | ||
|
|
3ed25c92cd | ||
|
|
f7644e6048 | ||
|
|
80afa6aff1 | ||
|
|
43de81929f | ||
|
|
e315e71d07 | ||
|
|
416eb34799 | ||
|
|
b7b13f51b2 | ||
|
|
2312187670 | ||
|
|
c7d0dc7c5f | ||
|
|
895cabee80 |
@@ -7,31 +7,77 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
5404AEEB24A90717003B2F54 /* PrefsShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E67E4524A8B0FE0025D261 /* PrefsShared.swift */; };
|
||||||
|
5404AEED24A95F3F003B2F54 /* SlideInAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5404AEEC24A95F3F003B2F54 /* SlideInAnimation.swift */; };
|
||||||
|
5404AEEF24ACC089003B2F54 /* VCAnalysisBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5404AEEE24ACC089003B2F54 /* VCAnalysisBar.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 */; };
|
||||||
|
541075CE24C9D43A00D6F1BF /* UNNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541075CD24C9D43A00D6F1BF /* UNNotification.swift */; };
|
||||||
|
541075CF24C9D43A00D6F1BF /* UNNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541075CD24C9D43A00D6F1BF /* UNNotification.swift */; };
|
||||||
|
541075D124CDBA0000D6F1BF /* ThrottledBatchQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541075D024CDBA0000D6F1BF /* ThrottledBatchQueue.swift */; };
|
||||||
|
541075D224CDBA0000D6F1BF /* ThrottledBatchQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541075D024CDBA0000D6F1BF /* ThrottledBatchQueue.swift */; };
|
||||||
|
541075D524CE286200D6F1BF /* CachedConnectionAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541075D424CE286200D6F1BF /* CachedConnectionAlert.swift */; };
|
||||||
|
541075D624CE286200D6F1BF /* CachedConnectionAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541075D424CE286200D6F1BF /* CachedConnectionAlert.swift */; };
|
||||||
|
541075D924CE2C7200D6F1BF /* GlassVPNHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541075D824CE2C7200D6F1BF /* GlassVPNHook.swift */; };
|
||||||
|
541075DA24CE2C7200D6F1BF /* GlassVPNHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541075D824CE2C7200D6F1BF /* GlassVPNHook.swift */; };
|
||||||
5412F8EE24571B8200A63D7A /* VCDateFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5412F8ED24571B8100A63D7A /* VCDateFilter.swift */; };
|
5412F8EE24571B8200A63D7A /* VCDateFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5412F8ED24571B8100A63D7A /* VCDateFilter.swift */; };
|
||||||
|
5412FCC224C628FA000DE429 /* TVCReminderAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5412FCBF24C628F9000DE429 /* TVCReminderAlerts.swift */; };
|
||||||
|
5412FCC324C628FA000DE429 /* TVCChooseAlertTone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5412FCC024C628F9000DE429 /* TVCChooseAlertTone.swift */; };
|
||||||
|
5412FCC424C628FA000DE429 /* TVCConnectionAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5412FCC124C628FA000DE429 /* TVCConnectionAlerts.swift */; };
|
||||||
541A957623E602DF00C09C19 /* LaunchIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 541A957523E602DF00C09C19 /* LaunchIcon.png */; };
|
541A957623E602DF00C09C19 /* LaunchIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 541A957523E602DF00C09C19 /* LaunchIcon.png */; };
|
||||||
541AC5D82399498A00A769D7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541AC5D72399498A00A769D7 /* AppDelegate.swift */; };
|
541AC5D82399498A00A769D7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541AC5D72399498A00A769D7 /* AppDelegate.swift */; };
|
||||||
541AC5DD2399498A00A769D7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 541AC5DB2399498A00A769D7 /* Main.storyboard */; };
|
541AC5DD2399498A00A769D7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 541AC5DB2399498A00A769D7 /* Main.storyboard */; };
|
||||||
541AC5DF2399498B00A769D7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 541AC5DE2399498B00A769D7 /* Assets.xcassets */; };
|
541AC5DF2399498B00A769D7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 541AC5DE2399498B00A769D7 /* Assets.xcassets */; };
|
||||||
541AC5E22399498B00A769D7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 541AC5E02399498B00A769D7 /* LaunchScreen.storyboard */; };
|
541AC5E22399498B00A769D7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 541AC5E02399498B00A769D7 /* LaunchScreen.storyboard */; };
|
||||||
|
541DCA6124A6B0F6005F1A4B /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541DCA6024A6B0F6005F1A4B /* Color.swift */; };
|
||||||
541FC47624A12D01009154D8 /* IBViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541FC47524A12D01009154D8 /* IBViews.swift */; };
|
541FC47624A12D01009154D8 /* IBViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541FC47524A12D01009154D8 /* IBViews.swift */; };
|
||||||
541FC47824A1453F009154D8 /* VCCoOccurrence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541FC47724A1453F009154D8 /* VCCoOccurrence.swift */; };
|
541FC47824A1453F009154D8 /* VCCoOccurrence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541FC47724A1453F009154D8 /* VCCoOccurrence.swift */; };
|
||||||
541FC9872497D81C00962623 /* TheGreatDestroyer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541FC9862497D81C00962623 /* TheGreatDestroyer.swift */; };
|
541FC9872497D81C00962623 /* TheGreatDestroyer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541FC9862497D81C00962623 /* TheGreatDestroyer.swift */; };
|
||||||
542E2A982404973F001462DC /* TBCMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542E2A972404973F001462DC /* TBCMain.swift */; };
|
542E2A982404973F001462DC /* TBCMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542E2A972404973F001462DC /* TBCMain.swift */; };
|
||||||
542E2A9A24051556001462DC /* TVCSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542E2A9924051556001462DC /* TVCSettings.swift */; };
|
543078AA24B5E12500278F2D /* snap2.caf in Resources */ = {isa = PBXBuildFile; fileRef = 5430789F24B5E12200278F2D /* snap2.caf */; };
|
||||||
|
543078AB24B5E12500278F2D /* snap2.caf in Resources */ = {isa = PBXBuildFile; fileRef = 5430789F24B5E12200278F2D /* snap2.caf */; };
|
||||||
|
543078AC24B5E12500278F2D /* typewriter2.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A024B5E12200278F2D /* typewriter2.caf */; };
|
||||||
|
543078AD24B5E12500278F2D /* typewriter2.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A024B5E12200278F2D /* typewriter2.caf */; };
|
||||||
|
543078AE24B5E12500278F2D /* wood1.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A124B5E12300278F2D /* wood1.caf */; };
|
||||||
|
543078AF24B5E12500278F2D /* wood1.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A124B5E12300278F2D /* wood1.caf */; };
|
||||||
|
543078B024B5E12500278F2D /* plop2.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A224B5E12300278F2D /* plop2.caf */; };
|
||||||
|
543078B124B5E12500278F2D /* plop2.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A224B5E12300278F2D /* plop2.caf */; };
|
||||||
|
543078B224B5E12500278F2D /* plop1.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A324B5E12300278F2D /* plop1.caf */; };
|
||||||
|
543078B324B5E12500278F2D /* plop1.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A324B5E12300278F2D /* plop1.caf */; };
|
||||||
|
543078B424B5E12500278F2D /* snap1.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A424B5E12300278F2D /* snap1.caf */; };
|
||||||
|
543078B524B5E12500278F2D /* snap1.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A424B5E12300278F2D /* snap1.caf */; };
|
||||||
|
543078B624B5E12500278F2D /* drum1.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A524B5E12300278F2D /* drum1.caf */; };
|
||||||
|
543078B724B5E12500278F2D /* drum1.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A524B5E12300278F2D /* drum1.caf */; };
|
||||||
|
543078B824B5E12500278F2D /* wood2.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A624B5E12400278F2D /* wood2.caf */; };
|
||||||
|
543078B924B5E12500278F2D /* wood2.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A624B5E12400278F2D /* wood2.caf */; };
|
||||||
|
543078BA24B5E12500278F2D /* typewriter1.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A724B5E12400278F2D /* typewriter1.caf */; };
|
||||||
|
543078BB24B5E12500278F2D /* typewriter1.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A724B5E12400278F2D /* typewriter1.caf */; };
|
||||||
|
543078BC24B5E12500278F2D /* clock.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A824B5E12400278F2D /* clock.caf */; };
|
||||||
|
543078BD24B5E12500278F2D /* clock.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A824B5E12400278F2D /* clock.caf */; };
|
||||||
|
543078BE24B5E12500278F2D /* drum2.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A924B5E12500278F2D /* drum2.caf */; };
|
||||||
|
543078BF24B5E12500278F2D /* drum2.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A924B5E12500278F2D /* drum2.caf */; };
|
||||||
|
543078C324B60F3B00278F2D /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 543078C124B60F3B00278F2D /* Settings.storyboard */; };
|
||||||
|
543078C924B75CEA00278F2D /* PushNotificationAppOnly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 543078C824B75CD100278F2D /* PushNotificationAppOnly.swift */; };
|
||||||
543CDB2023EEE61900B7F323 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 543CDB1F23EEE61900B7F323 /* PacketTunnelProvider.swift */; };
|
543CDB2023EEE61900B7F323 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 543CDB1F23EEE61900B7F323 /* PacketTunnelProvider.swift */; };
|
||||||
543CDB2523EEE61900B7F323 /* GlassVPN.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 543CDB1D23EEE61900B7F323 /* GlassVPN.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
543CDB2523EEE61900B7F323 /* GlassVPN.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 543CDB1D23EEE61900B7F323 /* GlassVPN.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
54448A2E2486464F00771C96 /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54448A2D2486464F00771C96 /* Array.swift */; };
|
54448A2E2486464F00771C96 /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54448A2D2486464F00771C96 /* Array.swift */; };
|
||||||
54448A30248647D900771C96 /* Time.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54448A2F248647D900771C96 /* Time.swift */; };
|
54448A30248647D900771C96 /* Time.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54448A2F248647D900771C96 /* Time.swift */; };
|
||||||
54448A3224899A4000771C96 /* SearchBarManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54448A3124899A4000771C96 /* SearchBarManager.swift */; };
|
54448A3224899A4000771C96 /* SearchBarManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54448A3124899A4000771C96 /* SearchBarManager.swift */; };
|
||||||
544C95262407B1C700AB89D0 /* SharedState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 544C95252407B1C700AB89D0 /* SharedState.swift */; };
|
544F912024A67EC5001D4B00 /* TVCOccurrenceContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 544F911F24A67EC5001D4B00 /* TVCOccurrenceContext.swift */; };
|
||||||
5458EBC0243A3F2200CFEB15 /* TVCRecordingDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5458EBBF243A3F2200CFEB15 /* TVCRecordingDetails.swift */; };
|
5458EBC0243A3F2200CFEB15 /* TVCRecordingDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5458EBBF243A3F2200CFEB15 /* TVCRecordingDetails.swift */; };
|
||||||
545DDDCF243E6267003B6544 /* TutorialSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DDDCE243E6267003B6544 /* TutorialSheet.swift */; };
|
545DDDCF243E6267003B6544 /* TutorialSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DDDCE243E6267003B6544 /* TutorialSheet.swift */; };
|
||||||
545DDDD124436983003B6544 /* QuickUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DDDD024436983003B6544 /* QuickUI.swift */; };
|
545DDDD124436983003B6544 /* QuickUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DDDD024436983003B6544 /* QuickUI.swift */; };
|
||||||
545DDDD424466D37003B6544 /* AutoLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DDDD324466D37003B6544 /* AutoLayout.swift */; };
|
545DDDD424466D37003B6544 /* AutoLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DDDD324466D37003B6544 /* AutoLayout.swift */; };
|
||||||
546063E523FEFAFE008F505A /* DBCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B7562223D7B2DC008F0C41 /* DBCore.swift */; };
|
546063E523FEFAFE008F505A /* DBCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B7562223D7B2DC008F0C41 /* DBCore.swift */; };
|
||||||
|
54686A7624F8062C0084934D /* NotificationBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54686A7524F8062C0084934D /* NotificationBanner.swift */; };
|
||||||
|
54686A8524FD0A3F0084934D /* tut-recording-howto.md in Resources */ = {isa = PBXBuildFile; fileRef = 54686A8424FD0A3F0084934D /* tut-recording-howto.md */; };
|
||||||
|
54686A8724FD27AA0084934D /* TinyMarkdown.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54686A8624FD26410084934D /* TinyMarkdown.swift */; };
|
||||||
|
54686A8D24FD428C0084934D /* tut-welcome-1.md in Resources */ = {isa = PBXBuildFile; fileRef = 54686A8824FD31580084934D /* tut-welcome-1.md */; };
|
||||||
|
54686A8E24FD42950084934D /* tut-welcome-2.md in Resources */ = {isa = PBXBuildFile; fileRef = 54686A8B24FD3F180084934D /* tut-welcome-2.md */; };
|
||||||
|
54686A8F24FD42950084934D /* tut-recording-1.md in Resources */ = {isa = PBXBuildFile; fileRef = 54686A8A24FD3F100084934D /* tut-recording-1.md */; };
|
||||||
|
54686A9024FD42950084934D /* tut-recording-2.md in Resources */ = {isa = PBXBuildFile; fileRef = 54686A8924FD31630084934D /* tut-recording-2.md */; };
|
||||||
|
54686A9124FD42950084934D /* tut-cooccurrence.md in Resources */ = {isa = PBXBuildFile; fileRef = 54686A8C24FD3F630084934D /* tut-cooccurrence.md */; };
|
||||||
54751E512423955100168273 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54751E502423955000168273 /* URL.swift */; };
|
54751E512423955100168273 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54751E502423955000168273 /* URL.swift */; };
|
||||||
54751E522423955100168273 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54751E502423955000168273 /* URL.swift */; };
|
54751E522423955100168273 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54751E502423955000168273 /* URL.swift */; };
|
||||||
54953E3323DC752E0054345C /* DBCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B7562223D7B2DC008F0C41 /* DBCore.swift */; };
|
54953E3323DC752E0054345C /* DBCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B7562223D7B2DC008F0C41 /* DBCore.swift */; };
|
||||||
@@ -39,23 +85,27 @@
|
|||||||
54953E6123E0D69A0054345C /* TVCHosts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54953E6023E0D69A0054345C /* TVCHosts.swift */; };
|
54953E6123E0D69A0054345C /* TVCHosts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54953E6023E0D69A0054345C /* TVCHosts.swift */; };
|
||||||
54953E6F23E44CD00054345C /* TVCHostDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54953E6E23E44CD00054345C /* TVCHostDetails.swift */; };
|
54953E6F23E44CD00054345C /* TVCHostDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54953E6E23E44CD00054345C /* TVCHostDetails.swift */; };
|
||||||
54953E7123E473F10054345C /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 54953E7023E473F10054345C /* Settings.bundle */; };
|
54953E7123E473F10054345C /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 54953E7023E473F10054345C /* Settings.bundle */; };
|
||||||
|
549A96D62501198400C565FA /* VCEditText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549A96D52501198400C565FA /* VCEditText.swift */; };
|
||||||
|
549A96DA250419B200C565FA /* CoOccurrence.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 549A96D8250419B200C565FA /* CoOccurrence.storyboard */; };
|
||||||
|
549D6ED524D5BFDB0032E498 /* TVCAppSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549D6ED424D5BFDB0032E498 /* TVCAppSearch.swift */; };
|
||||||
|
549ECD9D24A7AD550097571C /* CustomAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549ECD9C24A7AD550097571C /* CustomAlert.swift */; };
|
||||||
|
54A0CC0924E30C56009B5EC1 /* Recordings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 54A0CC0724E30C56009B5EC1 /* Recordings.storyboard */; };
|
||||||
|
54A0CC0C24E30D6F009B5EC1 /* Requests.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 54A0CC0A24E30D6F009B5EC1 /* Requests.storyboard */; };
|
||||||
54B34594240E6343004C53CC /* TVCFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B34593240E6343004C53CC /* TVCFilter.swift */; };
|
54B34594240E6343004C53CC /* TVCFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B34593240E6343004C53CC /* TVCFilter.swift */; };
|
||||||
54B34596240F0513004C53CC /* TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B34595240F0513004C53CC /* TableView.swift */; };
|
54B34596240F0513004C53CC /* TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B34595240F0513004C53CC /* TableView.swift */; };
|
||||||
54B345A6241BB982004C53CC /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B345A5241BB982004C53CC /* Notifications.swift */; };
|
54B345A6241BB982004C53CC /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B345A5241BB982004C53CC /* Notifications.swift */; };
|
||||||
54B345A9241BBA0B004C53CC /* Generic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B345A8241BBA0B004C53CC /* Generic.swift */; };
|
54B345A9241BBA0B004C53CC /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B345A8241BBA0B004C53CC /* Logging.swift */; };
|
||||||
54B345AB241BBA5B004C53CC /* AlertSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B345AA241BBA5B004C53CC /* AlertSheet.swift */; };
|
54B345AB241BBA5B004C53CC /* AlertSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B345AA241BBA5B004C53CC /* AlertSheet.swift */; };
|
||||||
54B345AD241BBB00004C53CC /* DBExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B345AC241BBB00004C53CC /* DBExtensions.swift */; };
|
54B345AD241BBB00004C53CC /* DBExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B345AC241BBB00004C53CC /* DBExtensions.swift */; };
|
||||||
54B345B0242264F8004C53CC /* third-level.txt in Resources */ = {isa = PBXBuildFile; fileRef = 54B345AF242264F8004C53CC /* third-level.txt */; };
|
54B345B0242264F8004C53CC /* third-level.txt in Resources */ = {isa = PBXBuildFile; fileRef = 54B345AF242264F8004C53CC /* third-level.txt */; };
|
||||||
54C056DB23E9E36E00214A3F /* AppInfoType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54C056DA23E9E36E00214A3F /* AppInfoType.swift */; };
|
54C056DB23E9E36E00214A3F /* AppStoreSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54C056DA23E9E36E00214A3F /* AppStoreSearch.swift */; };
|
||||||
54C056DD23E9EEF700214A3F /* BundleIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54C056DC23E9EEF700214A3F /* BundleIcon.swift */; };
|
54C056DD23E9EEF700214A3F /* BundleIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54C056DC23E9EEF700214A3F /* BundleIcon.swift */; };
|
||||||
54CA01D32426B23D003A5E04 /* Resolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01D22426B23D003A5E04 /* Resolver.swift */; };
|
54CA01D32426B23D003A5E04 /* Resolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01D22426B23D003A5E04 /* Resolver.swift */; };
|
||||||
54CA01D52426B252003A5E04 /* SafeDict.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01D42426B251003A5E04 /* SafeDict.swift */; };
|
54CA01D52426B252003A5E04 /* SafeDict.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01D42426B251003A5E04 /* SafeDict.swift */; };
|
||||||
54CA025C2426B2FD003A5E04 /* ConnectSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01E22426B2FC003A5E04 /* ConnectSession.swift */; };
|
54CA025C2426B2FD003A5E04 /* ConnectSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01E22426B2FC003A5E04 /* ConnectSession.swift */; };
|
||||||
54CA025D2426B2FD003A5E04 /* HTTPHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01E32426B2FC003A5E04 /* HTTPHeader.swift */; };
|
54CA025D2426B2FD003A5E04 /* HTTPHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01E32426B2FC003A5E04 /* HTTPHeader.swift */; };
|
||||||
54CA025E2426B2FD003A5E04 /* ResponseGeneratorFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01E42426B2FC003A5E04 /* ResponseGeneratorFactory.swift */; };
|
|
||||||
54CA025F2426B2FD003A5E04 /* ProxyServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01E62426B2FC003A5E04 /* ProxyServer.swift */; };
|
54CA025F2426B2FD003A5E04 /* ProxyServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01E62426B2FC003A5E04 /* ProxyServer.swift */; };
|
||||||
54CA02602426B2FD003A5E04 /* GCDProxyServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01E72426B2FC003A5E04 /* GCDProxyServer.swift */; };
|
54CA02602426B2FD003A5E04 /* GCDProxyServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01E72426B2FC003A5E04 /* GCDProxyServer.swift */; };
|
||||||
54CA02612426B2FD003A5E04 /* GCDSOCKS5ProxyServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01E82426B2FC003A5E04 /* GCDSOCKS5ProxyServer.swift */; };
|
|
||||||
54CA02622426B2FD003A5E04 /* GCDHTTPProxyServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01E92426B2FC003A5E04 /* GCDHTTPProxyServer.swift */; };
|
54CA02622426B2FD003A5E04 /* GCDHTTPProxyServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01E92426B2FC003A5E04 /* GCDHTTPProxyServer.swift */; };
|
||||||
54CA02662426B2FD003A5E04 /* NWUDPSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01EF2426B2FC003A5E04 /* NWUDPSocket.swift */; };
|
54CA02662426B2FD003A5E04 /* NWUDPSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01EF2426B2FC003A5E04 /* NWUDPSocket.swift */; };
|
||||||
54CA02672426B2FD003A5E04 /* RawTCPSocketProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01F02426B2FC003A5E04 /* RawTCPSocketProtocol.swift */; };
|
54CA02672426B2FD003A5E04 /* RawTCPSocketProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01F02426B2FC003A5E04 /* RawTCPSocketProtocol.swift */; };
|
||||||
@@ -77,15 +127,8 @@
|
|||||||
54CA027B2426B2FD003A5E04 /* HTTPAuthentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02062426B2FC003A5E04 /* HTTPAuthentication.swift */; };
|
54CA027B2426B2FD003A5E04 /* HTTPAuthentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02062426B2FC003A5E04 /* HTTPAuthentication.swift */; };
|
||||||
54CA027C2426B2FD003A5E04 /* StreamScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02072426B2FC003A5E04 /* StreamScanner.swift */; };
|
54CA027C2426B2FD003A5E04 /* StreamScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02072426B2FC003A5E04 /* StreamScanner.swift */; };
|
||||||
54CA027D2426B2FD003A5E04 /* GlobalIntializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02082426B2FC003A5E04 /* GlobalIntializer.swift */; };
|
54CA027D2426B2FD003A5E04 /* GlobalIntializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02082426B2FC003A5E04 /* GlobalIntializer.swift */; };
|
||||||
54CA027E2426B2FD003A5E04 /* DomainListRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA020A2426B2FC003A5E04 /* DomainListRule.swift */; };
|
|
||||||
54CA02802426B2FD003A5E04 /* DNSSessionMatchType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA020C2426B2FC003A5E04 /* DNSSessionMatchType.swift */; };
|
54CA02802426B2FD003A5E04 /* DNSSessionMatchType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA020C2426B2FC003A5E04 /* DNSSessionMatchType.swift */; };
|
||||||
54CA02812426B2FD003A5E04 /* DNSFailRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA020D2426B2FC003A5E04 /* DNSFailRule.swift */; };
|
|
||||||
54CA02822426B2FD003A5E04 /* AllRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA020E2426B2FC003A5E04 /* AllRule.swift */; };
|
|
||||||
54CA02832426B2FD003A5E04 /* DNSSessionMatchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA020F2426B2FC003A5E04 /* DNSSessionMatchResult.swift */; };
|
54CA02832426B2FD003A5E04 /* DNSSessionMatchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA020F2426B2FC003A5E04 /* DNSSessionMatchResult.swift */; };
|
||||||
54CA02842426B2FD003A5E04 /* Rule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02102426B2FC003A5E04 /* Rule.swift */; };
|
|
||||||
54CA02852426B2FD003A5E04 /* DirectRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02112426B2FC003A5E04 /* DirectRule.swift */; };
|
|
||||||
54CA02862426B2FD003A5E04 /* RuleManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02122426B2FC003A5E04 /* RuleManager.swift */; };
|
|
||||||
54CA02872426B2FD003A5E04 /* IPRangeListRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02132426B2FC003A5E04 /* IPRangeListRule.swift */; };
|
|
||||||
54CA02882426B2FD003A5E04 /* QueueFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02152426B2FC003A5E04 /* QueueFactory.swift */; };
|
54CA02882426B2FD003A5E04 /* QueueFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02152426B2FC003A5E04 /* QueueFactory.swift */; };
|
||||||
54CA02892426B2FD003A5E04 /* Tunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02162426B2FC003A5E04 /* Tunnel.swift */; };
|
54CA02892426B2FD003A5E04 /* Tunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02162426B2FC003A5E04 /* Tunnel.swift */; };
|
||||||
54CA028A2426B2FD003A5E04 /* ResponseGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02172426B2FC003A5E04 /* ResponseGenerator.swift */; };
|
54CA028A2426B2FD003A5E04 /* ResponseGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02172426B2FC003A5E04 /* ResponseGenerator.swift */; };
|
||||||
@@ -104,30 +147,19 @@
|
|||||||
54CA029E2426B2FD003A5E04 /* EventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02302426B2FC003A5E04 /* EventType.swift */; };
|
54CA029E2426B2FD003A5E04 /* EventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02302426B2FC003A5E04 /* EventType.swift */; };
|
||||||
54CA029F2426B2FD003A5E04 /* ProxySocketEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02312426B2FC003A5E04 /* ProxySocketEvent.swift */; };
|
54CA029F2426B2FD003A5E04 /* ProxySocketEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02312426B2FC003A5E04 /* ProxySocketEvent.swift */; };
|
||||||
54CA02A02426B2FD003A5E04 /* TunnelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02322426B2FC003A5E04 /* TunnelEvent.swift */; };
|
54CA02A02426B2FD003A5E04 /* TunnelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02322426B2FC003A5E04 /* TunnelEvent.swift */; };
|
||||||
54CA02A12426B2FD003A5E04 /* RuleMatchEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02332426B2FC003A5E04 /* RuleMatchEvent.swift */; };
|
|
||||||
54CA02A22426B2FD003A5E04 /* ObserverFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02342426B2FC003A5E04 /* ObserverFactory.swift */; };
|
54CA02A22426B2FD003A5E04 /* ObserverFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02342426B2FC003A5E04 /* ObserverFactory.swift */; };
|
||||||
54CA02A32426B2FD003A5E04 /* HTTPAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02372426B2FC003A5E04 /* HTTPAdapter.swift */; };
|
|
||||||
54CA02A42426B2FD003A5E04 /* SecureHTTPAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02382426B2FC003A5E04 /* SecureHTTPAdapter.swift */; };
|
|
||||||
54CA02A62426B2FD003A5E04 /* AdapterSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA023A2426B2FC003A5E04 /* AdapterSocket.swift */; };
|
54CA02A62426B2FD003A5E04 /* AdapterSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA023A2426B2FC003A5E04 /* AdapterSocket.swift */; };
|
||||||
54CA02A72426B2FD003A5E04 /* DirectAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA023B2426B2FC003A5E04 /* DirectAdapter.swift */; };
|
54CA02A72426B2FD003A5E04 /* DirectAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA023B2426B2FC003A5E04 /* DirectAdapter.swift */; };
|
||||||
54CA02A82426B2FD003A5E04 /* SOCKS5Adapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA023C2426B2FC003A5E04 /* SOCKS5Adapter.swift */; };
|
|
||||||
54CA02A92426B2FD003A5E04 /* RejectAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA023D2426B2FC003A5E04 /* RejectAdapter.swift */; };
|
|
||||||
54CA02AC2426B2FD003A5E04 /* AuthenticationServerAdapterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02412426B2FC003A5E04 /* AuthenticationServerAdapterFactory.swift */; };
|
|
||||||
54CA02AD2426B2FD003A5E04 /* RejectAdapterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02422426B2FC003A5E04 /* RejectAdapterFactory.swift */; };
|
|
||||||
54CA02AE2426B2FD003A5E04 /* AdapterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02432426B2FD003A5E04 /* AdapterFactory.swift */; };
|
54CA02AE2426B2FD003A5E04 /* AdapterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02432426B2FD003A5E04 /* AdapterFactory.swift */; };
|
||||||
54CA02AF2426B2FD003A5E04 /* SOCKS5AdapterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02442426B2FD003A5E04 /* SOCKS5AdapterFactory.swift */; };
|
|
||||||
54CA02B02426B2FD003A5E04 /* SecureHTTPAdapterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02452426B2FD003A5E04 /* SecureHTTPAdapterFactory.swift */; };
|
|
||||||
54CA02B12426B2FD003A5E04 /* ServerAdapterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02462426B2FD003A5E04 /* ServerAdapterFactory.swift */; };
|
|
||||||
54CA02B22426B2FD003A5E04 /* AdapterFactoryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02472426B2FD003A5E04 /* AdapterFactoryManager.swift */; };
|
|
||||||
54CA02B32426B2FD003A5E04 /* HTTPAdapterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02482426B2FD003A5E04 /* HTTPAdapterFactory.swift */; };
|
|
||||||
54CA02B82426B2FD003A5E04 /* HTTPProxySocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA024F2426B2FD003A5E04 /* HTTPProxySocket.swift */; };
|
54CA02B82426B2FD003A5E04 /* HTTPProxySocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA024F2426B2FD003A5E04 /* HTTPProxySocket.swift */; };
|
||||||
54CA02B92426B2FD003A5E04 /* DirectProxySocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02502426B2FD003A5E04 /* DirectProxySocket.swift */; };
|
|
||||||
54CA02BA2426B2FD003A5E04 /* ProxySocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02512426B2FD003A5E04 /* ProxySocket.swift */; };
|
54CA02BA2426B2FD003A5E04 /* ProxySocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02512426B2FD003A5E04 /* ProxySocket.swift */; };
|
||||||
54CA02BB2426B2FD003A5E04 /* SOCKS5ProxySocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02522426B2FD003A5E04 /* SOCKS5ProxySocket.swift */; };
|
|
||||||
54CA02BC2426B2FD003A5E04 /* SocketProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02532426B2FD003A5E04 /* SocketProtocol.swift */; };
|
54CA02BC2426B2FD003A5E04 /* SocketProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02532426B2FD003A5E04 /* SocketProtocol.swift */; };
|
||||||
54CA02BE2426D4F3003A5E04 /* DDLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02BD2426D4F3003A5E04 /* DDLog.swift */; };
|
54CA02BE2426D4F3003A5E04 /* DDLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02BD2426D4F3003A5E04 /* DDLog.swift */; };
|
||||||
54CA02C32426DCCD003A5E04 /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02BF2426DCCC003A5E04 /* GCDAsyncSocket.m */; };
|
54CA02C32426DCCD003A5E04 /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02BF2426DCCC003A5E04 /* GCDAsyncSocket.m */; };
|
||||||
54CA02C42426DCCD003A5E04 /* GCDAsyncUdpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02C02426DCCD003A5E04 /* GCDAsyncUdpSocket.m */; };
|
54CA02C42426DCCD003A5E04 /* GCDAsyncUdpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02C02426DCCD003A5E04 /* GCDAsyncUdpSocket.m */; };
|
||||||
|
54CE8BC424B1ED2100CC1756 /* PushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CE8BC324B1ED2100CC1756 /* PushNotification.swift */; };
|
||||||
|
54CE8BC524B1ED2100CC1756 /* PushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CE8BC324B1ED2100CC1756 /* PushNotification.swift */; };
|
||||||
|
54CFE86824E3F401001687DD /* TVCShareRecording.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CFE86724E3F401001687DD /* TVCShareRecording.swift */; };
|
||||||
54D8B97A246C9F2000EB2414 /* FilterPipeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D8B979246C9F2000EB2414 /* FilterPipeline.swift */; };
|
54D8B97A246C9F2000EB2414 /* FilterPipeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D8B979246C9F2000EB2414 /* FilterPipeline.swift */; };
|
||||||
54D8B97C2471A7E000EB2414 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D8B97B2471A7E000EB2414 /* String.swift */; };
|
54D8B97C2471A7E000EB2414 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D8B97B2471A7E000EB2414 /* String.swift */; };
|
||||||
54D8B97E2471B88900EB2414 /* DBCommon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D8B97D2471B88900EB2414 /* DBCommon.swift */; };
|
54D8B97E2471B88900EB2414 /* DBCommon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D8B97D2471B88900EB2414 /* DBCommon.swift */; };
|
||||||
@@ -135,10 +167,15 @@
|
|||||||
54D8B9832471BD8100EB2414 /* DBAppOnly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D8B9822471BD8100EB2414 /* DBAppOnly.swift */; };
|
54D8B9832471BD8100EB2414 /* DBAppOnly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D8B9822471BD8100EB2414 /* DBAppOnly.swift */; };
|
||||||
54D8B98624796E9900EB2414 /* GroupedDomainDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D8B98524796E9800EB2414 /* GroupedDomainDataSource.swift */; };
|
54D8B98624796E9900EB2414 /* GroupedDomainDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D8B98524796E9800EB2414 /* GroupedDomainDataSource.swift */; };
|
||||||
54E540F2247C423200F7C34A /* DomainFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E540F1247C423200F7C34A /* DomainFilter.swift */; };
|
54E540F2247C423200F7C34A /* DomainFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E540F1247C423200F7C34A /* DomainFilter.swift */; };
|
||||||
54E540F4247D3F2600F7C34A /* TestDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E540F3247D3F2600F7C34A /* TestDataSource.swift */; };
|
54E540F4247D3F2600F7C34A /* SimulatorVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E540F3247D3F2600F7C34A /* SimulatorVPN.swift */; };
|
||||||
54E540F8247DB90F00F7C34A /* RecordingsDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E540F7247DB90F00F7C34A /* RecordingsDB.swift */; };
|
54E540F8247DB90F00F7C34A /* RecordingsDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E540F7247DB90F00F7C34A /* RecordingsDB.swift */; };
|
||||||
54E540FA2482414800F7C34A /* SyncUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E540F92482414800F7C34A /* SyncUpdate.swift */; };
|
54E540FA2482414800F7C34A /* SyncUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E540F92482414800F7C34A /* SyncUpdate.swift */; };
|
||||||
54EFA4E6248EEE240022D618 /* DatePickerAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54EFA4E5248EEE240022D618 /* DatePickerAlert.swift */; };
|
54E67E4624A8B0FE0025D261 /* PrefsShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E67E4524A8B0FE0025D261 /* PrefsShared.swift */; };
|
||||||
|
54E67E4924A8B1280025D261 /* Prefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E67E4824A8B1280025D261 /* Prefs.swift */; };
|
||||||
|
54E67E4B24A8C6370025D261 /* GlassVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E67E4A24A8C6370025D261 /* GlassVPN.swift */; };
|
||||||
|
54E67E4D24A8E20D0025D261 /* TVCSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542E2A9924051556001462DC /* TVCSettings.swift */; };
|
||||||
|
54E67E4F24A8E2910025D261 /* Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E67E4E24A8E2910025D261 /* Equatable.swift */; };
|
||||||
|
54E67E5124A8E8820025D261 /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E67E5024A8E8820025D261 /* View.swift */; };
|
||||||
54EFA4E82491A16A0022D618 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54EFA4E72491A16A0022D618 /* Font.swift */; };
|
54EFA4E82491A16A0022D618 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54EFA4E72491A16A0022D618 /* Font.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
@@ -167,10 +204,19 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
5404AEEC24A95F3F003B2F54 /* SlideInAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideInAnimation.swift; sourceTree = "<group>"; };
|
||||||
|
5404AEEE24ACC089003B2F54 /* VCAnalysisBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCAnalysisBar.swift; sourceTree = "<group>"; };
|
||||||
540E677F242D2CF100871BBE /* VCRecordings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCRecordings.swift; sourceTree = "<group>"; };
|
540E677F242D2CF100871BBE /* VCRecordings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCRecordings.swift; sourceTree = "<group>"; };
|
||||||
540E67812433483D00871BBE /* VCEditRecording.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCEditRecording.swift; sourceTree = "<group>"; };
|
540E67812433483D00871BBE /* VCEditRecording.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCEditRecording.swift; sourceTree = "<group>"; };
|
||||||
540E67832433FAFE00871BBE /* TVCPreviousRecords.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCPreviousRecords.swift; sourceTree = "<group>"; };
|
540E67832433FAFE00871BBE /* TVCPreviousRecords.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCPreviousRecords.swift; sourceTree = "<group>"; };
|
||||||
|
541075CD24C9D43A00D6F1BF /* UNNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotification.swift; sourceTree = "<group>"; };
|
||||||
|
541075D024CDBA0000D6F1BF /* ThrottledBatchQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThrottledBatchQueue.swift; sourceTree = "<group>"; };
|
||||||
|
541075D424CE286200D6F1BF /* CachedConnectionAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedConnectionAlert.swift; sourceTree = "<group>"; };
|
||||||
|
541075D824CE2C7200D6F1BF /* GlassVPNHook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlassVPNHook.swift; sourceTree = "<group>"; };
|
||||||
5412F8ED24571B8100A63D7A /* VCDateFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCDateFilter.swift; sourceTree = "<group>"; };
|
5412F8ED24571B8100A63D7A /* VCDateFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCDateFilter.swift; sourceTree = "<group>"; };
|
||||||
|
5412FCBF24C628F9000DE429 /* TVCReminderAlerts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TVCReminderAlerts.swift; sourceTree = "<group>"; };
|
||||||
|
5412FCC024C628F9000DE429 /* TVCChooseAlertTone.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TVCChooseAlertTone.swift; sourceTree = "<group>"; };
|
||||||
|
5412FCC124C628FA000DE429 /* TVCConnectionAlerts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TVCConnectionAlerts.swift; sourceTree = "<group>"; };
|
||||||
541A957523E602DF00C09C19 /* LaunchIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = LaunchIcon.png; sourceTree = "<group>"; };
|
541A957523E602DF00C09C19 /* LaunchIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = LaunchIcon.png; sourceTree = "<group>"; };
|
||||||
541AC5D42399498A00A769D7 /* AppCheck.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppCheck.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
541AC5D42399498A00A769D7 /* AppCheck.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppCheck.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
541AC5D72399498A00A769D7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
541AC5D72399498A00A769D7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
@@ -178,11 +224,25 @@
|
|||||||
541AC5DE2399498B00A769D7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
541AC5DE2399498B00A769D7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
541AC5E12399498B00A769D7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
541AC5E12399498B00A769D7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
541AC5E32399498B00A769D7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
541AC5E32399498B00A769D7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
541DCA6024A6B0F6005F1A4B /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = "<group>"; };
|
||||||
541FC47524A12D01009154D8 /* IBViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBViews.swift; sourceTree = "<group>"; };
|
541FC47524A12D01009154D8 /* IBViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBViews.swift; sourceTree = "<group>"; };
|
||||||
541FC47724A1453F009154D8 /* VCCoOccurrence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCCoOccurrence.swift; sourceTree = "<group>"; };
|
541FC47724A1453F009154D8 /* VCCoOccurrence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCCoOccurrence.swift; sourceTree = "<group>"; };
|
||||||
541FC9862497D81C00962623 /* TheGreatDestroyer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TheGreatDestroyer.swift; sourceTree = "<group>"; };
|
541FC9862497D81C00962623 /* TheGreatDestroyer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TheGreatDestroyer.swift; sourceTree = "<group>"; };
|
||||||
542E2A972404973F001462DC /* TBCMain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TBCMain.swift; sourceTree = "<group>"; };
|
542E2A972404973F001462DC /* TBCMain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TBCMain.swift; sourceTree = "<group>"; };
|
||||||
542E2A9924051556001462DC /* TVCSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCSettings.swift; sourceTree = "<group>"; };
|
542E2A9924051556001462DC /* TVCSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCSettings.swift; sourceTree = "<group>"; };
|
||||||
|
5430789F24B5E12200278F2D /* snap2.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = snap2.caf; sourceTree = "<group>"; };
|
||||||
|
543078A024B5E12200278F2D /* typewriter2.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = typewriter2.caf; sourceTree = "<group>"; };
|
||||||
|
543078A124B5E12300278F2D /* wood1.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = wood1.caf; sourceTree = "<group>"; };
|
||||||
|
543078A224B5E12300278F2D /* plop2.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = plop2.caf; sourceTree = "<group>"; };
|
||||||
|
543078A324B5E12300278F2D /* plop1.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = plop1.caf; sourceTree = "<group>"; };
|
||||||
|
543078A424B5E12300278F2D /* snap1.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = snap1.caf; sourceTree = "<group>"; };
|
||||||
|
543078A524B5E12300278F2D /* drum1.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = drum1.caf; sourceTree = "<group>"; };
|
||||||
|
543078A624B5E12400278F2D /* wood2.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = wood2.caf; sourceTree = "<group>"; };
|
||||||
|
543078A724B5E12400278F2D /* typewriter1.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = typewriter1.caf; sourceTree = "<group>"; };
|
||||||
|
543078A824B5E12400278F2D /* clock.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = clock.caf; sourceTree = "<group>"; };
|
||||||
|
543078A924B5E12500278F2D /* drum2.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = drum2.caf; sourceTree = "<group>"; };
|
||||||
|
543078C224B60F3B00278F2D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Settings.storyboard; sourceTree = "<group>"; };
|
||||||
|
543078C824B75CD100278F2D /* PushNotificationAppOnly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationAppOnly.swift; sourceTree = "<group>"; };
|
||||||
543CDB1D23EEE61900B7F323 /* GlassVPN.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = GlassVPN.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
543CDB1D23EEE61900B7F323 /* GlassVPN.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = GlassVPN.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
543CDB1F23EEE61900B7F323 /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = "<group>"; };
|
543CDB1F23EEE61900B7F323 /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = "<group>"; };
|
||||||
543CDB2123EEE61900B7F323 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
543CDB2123EEE61900B7F323 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
@@ -190,36 +250,48 @@
|
|||||||
54448A2D2486464F00771C96 /* Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = "<group>"; };
|
54448A2D2486464F00771C96 /* Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = "<group>"; };
|
||||||
54448A2F248647D900771C96 /* Time.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Time.swift; sourceTree = "<group>"; };
|
54448A2F248647D900771C96 /* Time.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Time.swift; sourceTree = "<group>"; };
|
||||||
54448A3124899A4000771C96 /* SearchBarManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBarManager.swift; sourceTree = "<group>"; };
|
54448A3124899A4000771C96 /* SearchBarManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBarManager.swift; sourceTree = "<group>"; };
|
||||||
544C95252407B1C700AB89D0 /* SharedState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedState.swift; sourceTree = "<group>"; };
|
544F911F24A67EC5001D4B00 /* TVCOccurrenceContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCOccurrenceContext.swift; sourceTree = "<group>"; };
|
||||||
5458EBBF243A3F2200CFEB15 /* TVCRecordingDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCRecordingDetails.swift; sourceTree = "<group>"; };
|
5458EBBF243A3F2200CFEB15 /* TVCRecordingDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCRecordingDetails.swift; sourceTree = "<group>"; };
|
||||||
545DDDCE243E6267003B6544 /* TutorialSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TutorialSheet.swift; sourceTree = "<group>"; };
|
545DDDCE243E6267003B6544 /* TutorialSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TutorialSheet.swift; sourceTree = "<group>"; };
|
||||||
545DDDD024436983003B6544 /* QuickUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickUI.swift; sourceTree = "<group>"; };
|
545DDDD024436983003B6544 /* QuickUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickUI.swift; sourceTree = "<group>"; };
|
||||||
545DDDD324466D37003B6544 /* AutoLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoLayout.swift; sourceTree = "<group>"; };
|
545DDDD324466D37003B6544 /* AutoLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoLayout.swift; sourceTree = "<group>"; };
|
||||||
|
54686A7524F8062C0084934D /* NotificationBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationBanner.swift; sourceTree = "<group>"; };
|
||||||
|
54686A8424FD0A3F0084934D /* tut-recording-howto.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "tut-recording-howto.md"; sourceTree = "<group>"; };
|
||||||
|
54686A8624FD26410084934D /* TinyMarkdown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TinyMarkdown.swift; sourceTree = "<group>"; };
|
||||||
|
54686A8824FD31580084934D /* tut-welcome-1.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "tut-welcome-1.md"; sourceTree = "<group>"; };
|
||||||
|
54686A8924FD31630084934D /* tut-recording-2.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "tut-recording-2.md"; sourceTree = "<group>"; };
|
||||||
|
54686A8A24FD3F100084934D /* tut-recording-1.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "tut-recording-1.md"; sourceTree = "<group>"; };
|
||||||
|
54686A8B24FD3F180084934D /* tut-welcome-2.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "tut-welcome-2.md"; sourceTree = "<group>"; };
|
||||||
|
54686A8C24FD3F630084934D /* tut-cooccurrence.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "tut-cooccurrence.md"; sourceTree = "<group>"; };
|
||||||
54751E502423955000168273 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; };
|
54751E502423955000168273 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; };
|
||||||
548B1F9423D338EC005B047C /* main.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = main.entitlements; sourceTree = "<group>"; };
|
548B1F9423D338EC005B047C /* main.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = main.entitlements; sourceTree = "<group>"; };
|
||||||
54953E5E23DEBE840054345C /* TVCDomains.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCDomains.swift; sourceTree = "<group>"; };
|
54953E5E23DEBE840054345C /* TVCDomains.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCDomains.swift; sourceTree = "<group>"; };
|
||||||
54953E6023E0D69A0054345C /* TVCHosts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCHosts.swift; sourceTree = "<group>"; };
|
54953E6023E0D69A0054345C /* TVCHosts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCHosts.swift; sourceTree = "<group>"; };
|
||||||
54953E6E23E44CD00054345C /* TVCHostDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCHostDetails.swift; sourceTree = "<group>"; };
|
54953E6E23E44CD00054345C /* TVCHostDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCHostDetails.swift; sourceTree = "<group>"; };
|
||||||
54953E7023E473F10054345C /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
|
54953E7023E473F10054345C /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
|
||||||
|
549A96D52501198400C565FA /* VCEditText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCEditText.swift; sourceTree = "<group>"; };
|
||||||
|
549A96D9250419B200C565FA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/CoOccurrence.storyboard; sourceTree = "<group>"; };
|
||||||
|
549D6ED424D5BFDB0032E498 /* TVCAppSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCAppSearch.swift; sourceTree = "<group>"; };
|
||||||
|
549ECD9C24A7AD550097571C /* CustomAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAlert.swift; sourceTree = "<group>"; };
|
||||||
|
54A0CC0824E30C56009B5EC1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Recordings.storyboard; sourceTree = "<group>"; };
|
||||||
|
54A0CC0B24E30D6F009B5EC1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Requests.storyboard; sourceTree = "<group>"; };
|
||||||
54B34593240E6343004C53CC /* TVCFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCFilter.swift; sourceTree = "<group>"; };
|
54B34593240E6343004C53CC /* TVCFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCFilter.swift; sourceTree = "<group>"; };
|
||||||
54B34595240F0513004C53CC /* TableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableView.swift; sourceTree = "<group>"; };
|
54B34595240F0513004C53CC /* TableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableView.swift; sourceTree = "<group>"; };
|
||||||
54B345A5241BB982004C53CC /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = "<group>"; };
|
54B345A5241BB982004C53CC /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = "<group>"; };
|
||||||
54B345A8241BBA0B004C53CC /* Generic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Generic.swift; sourceTree = "<group>"; };
|
54B345A8241BBA0B004C53CC /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = "<group>"; };
|
||||||
54B345AA241BBA5B004C53CC /* AlertSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertSheet.swift; sourceTree = "<group>"; };
|
54B345AA241BBA5B004C53CC /* AlertSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertSheet.swift; sourceTree = "<group>"; };
|
||||||
54B345AC241BBB00004C53CC /* DBExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBExtensions.swift; sourceTree = "<group>"; };
|
54B345AC241BBB00004C53CC /* DBExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBExtensions.swift; sourceTree = "<group>"; };
|
||||||
54B345AF242264F8004C53CC /* third-level.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "third-level.txt"; sourceTree = "<group>"; };
|
54B345AF242264F8004C53CC /* third-level.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "third-level.txt"; sourceTree = "<group>"; };
|
||||||
54B7562223D7B2DC008F0C41 /* DBCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBCore.swift; sourceTree = "<group>"; };
|
54B7562223D7B2DC008F0C41 /* DBCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBCore.swift; sourceTree = "<group>"; };
|
||||||
54C056DA23E9E36E00214A3F /* AppInfoType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoType.swift; sourceTree = "<group>"; };
|
54C056DA23E9E36E00214A3F /* AppStoreSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStoreSearch.swift; sourceTree = "<group>"; };
|
||||||
54C056DC23E9EEF700214A3F /* BundleIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleIcon.swift; sourceTree = "<group>"; };
|
54C056DC23E9EEF700214A3F /* BundleIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleIcon.swift; sourceTree = "<group>"; };
|
||||||
54CA00D62426A803003A5E04 /* CocoaAsyncSocket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CocoaAsyncSocket.h; sourceTree = "<group>"; };
|
54CA00D62426A803003A5E04 /* CocoaAsyncSocket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CocoaAsyncSocket.h; sourceTree = "<group>"; };
|
||||||
54CA01D22426B23D003A5E04 /* Resolver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Resolver.swift; sourceTree = "<group>"; };
|
54CA01D22426B23D003A5E04 /* Resolver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Resolver.swift; sourceTree = "<group>"; };
|
||||||
54CA01D42426B251003A5E04 /* SafeDict.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SafeDict.swift; sourceTree = "<group>"; };
|
54CA01D42426B251003A5E04 /* SafeDict.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SafeDict.swift; sourceTree = "<group>"; };
|
||||||
54CA01E22426B2FC003A5E04 /* ConnectSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectSession.swift; sourceTree = "<group>"; };
|
54CA01E22426B2FC003A5E04 /* ConnectSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectSession.swift; sourceTree = "<group>"; };
|
||||||
54CA01E32426B2FC003A5E04 /* HTTPHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPHeader.swift; sourceTree = "<group>"; };
|
54CA01E32426B2FC003A5E04 /* HTTPHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPHeader.swift; sourceTree = "<group>"; };
|
||||||
54CA01E42426B2FC003A5E04 /* ResponseGeneratorFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResponseGeneratorFactory.swift; sourceTree = "<group>"; };
|
|
||||||
54CA01E62426B2FC003A5E04 /* ProxyServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProxyServer.swift; sourceTree = "<group>"; };
|
54CA01E62426B2FC003A5E04 /* ProxyServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProxyServer.swift; sourceTree = "<group>"; };
|
||||||
54CA01E72426B2FC003A5E04 /* GCDProxyServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GCDProxyServer.swift; sourceTree = "<group>"; };
|
54CA01E72426B2FC003A5E04 /* GCDProxyServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GCDProxyServer.swift; sourceTree = "<group>"; };
|
||||||
54CA01E82426B2FC003A5E04 /* GCDSOCKS5ProxyServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GCDSOCKS5ProxyServer.swift; sourceTree = "<group>"; };
|
|
||||||
54CA01E92426B2FC003A5E04 /* GCDHTTPProxyServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GCDHTTPProxyServer.swift; sourceTree = "<group>"; };
|
54CA01E92426B2FC003A5E04 /* GCDHTTPProxyServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GCDHTTPProxyServer.swift; sourceTree = "<group>"; };
|
||||||
54CA01EF2426B2FC003A5E04 /* NWUDPSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NWUDPSocket.swift; sourceTree = "<group>"; };
|
54CA01EF2426B2FC003A5E04 /* NWUDPSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NWUDPSocket.swift; sourceTree = "<group>"; };
|
||||||
54CA01F02426B2FC003A5E04 /* RawTCPSocketProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RawTCPSocketProtocol.swift; sourceTree = "<group>"; };
|
54CA01F02426B2FC003A5E04 /* RawTCPSocketProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RawTCPSocketProtocol.swift; sourceTree = "<group>"; };
|
||||||
@@ -241,15 +313,8 @@
|
|||||||
54CA02062426B2FC003A5E04 /* HTTPAuthentication.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPAuthentication.swift; sourceTree = "<group>"; };
|
54CA02062426B2FC003A5E04 /* HTTPAuthentication.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPAuthentication.swift; sourceTree = "<group>"; };
|
||||||
54CA02072426B2FC003A5E04 /* StreamScanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StreamScanner.swift; sourceTree = "<group>"; };
|
54CA02072426B2FC003A5E04 /* StreamScanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StreamScanner.swift; sourceTree = "<group>"; };
|
||||||
54CA02082426B2FC003A5E04 /* GlobalIntializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalIntializer.swift; sourceTree = "<group>"; };
|
54CA02082426B2FC003A5E04 /* GlobalIntializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalIntializer.swift; sourceTree = "<group>"; };
|
||||||
54CA020A2426B2FC003A5E04 /* DomainListRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainListRule.swift; sourceTree = "<group>"; };
|
|
||||||
54CA020C2426B2FC003A5E04 /* DNSSessionMatchType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DNSSessionMatchType.swift; sourceTree = "<group>"; };
|
54CA020C2426B2FC003A5E04 /* DNSSessionMatchType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DNSSessionMatchType.swift; sourceTree = "<group>"; };
|
||||||
54CA020D2426B2FC003A5E04 /* DNSFailRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DNSFailRule.swift; sourceTree = "<group>"; };
|
|
||||||
54CA020E2426B2FC003A5E04 /* AllRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AllRule.swift; sourceTree = "<group>"; };
|
|
||||||
54CA020F2426B2FC003A5E04 /* DNSSessionMatchResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DNSSessionMatchResult.swift; sourceTree = "<group>"; };
|
54CA020F2426B2FC003A5E04 /* DNSSessionMatchResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DNSSessionMatchResult.swift; sourceTree = "<group>"; };
|
||||||
54CA02102426B2FC003A5E04 /* Rule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Rule.swift; sourceTree = "<group>"; };
|
|
||||||
54CA02112426B2FC003A5E04 /* DirectRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectRule.swift; sourceTree = "<group>"; };
|
|
||||||
54CA02122426B2FC003A5E04 /* RuleManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleManager.swift; sourceTree = "<group>"; };
|
|
||||||
54CA02132426B2FC003A5E04 /* IPRangeListRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IPRangeListRule.swift; sourceTree = "<group>"; };
|
|
||||||
54CA02152426B2FC003A5E04 /* QueueFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueueFactory.swift; sourceTree = "<group>"; };
|
54CA02152426B2FC003A5E04 /* QueueFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueueFactory.swift; sourceTree = "<group>"; };
|
||||||
54CA02162426B2FC003A5E04 /* Tunnel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tunnel.swift; sourceTree = "<group>"; };
|
54CA02162426B2FC003A5E04 /* Tunnel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tunnel.swift; sourceTree = "<group>"; };
|
||||||
54CA02172426B2FC003A5E04 /* ResponseGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResponseGenerator.swift; sourceTree = "<group>"; };
|
54CA02172426B2FC003A5E04 /* ResponseGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResponseGenerator.swift; sourceTree = "<group>"; };
|
||||||
@@ -268,42 +333,34 @@
|
|||||||
54CA02302426B2FC003A5E04 /* EventType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventType.swift; sourceTree = "<group>"; };
|
54CA02302426B2FC003A5E04 /* EventType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventType.swift; sourceTree = "<group>"; };
|
||||||
54CA02312426B2FC003A5E04 /* ProxySocketEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProxySocketEvent.swift; sourceTree = "<group>"; };
|
54CA02312426B2FC003A5E04 /* ProxySocketEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProxySocketEvent.swift; sourceTree = "<group>"; };
|
||||||
54CA02322426B2FC003A5E04 /* TunnelEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelEvent.swift; sourceTree = "<group>"; };
|
54CA02322426B2FC003A5E04 /* TunnelEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelEvent.swift; sourceTree = "<group>"; };
|
||||||
54CA02332426B2FC003A5E04 /* RuleMatchEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleMatchEvent.swift; sourceTree = "<group>"; };
|
|
||||||
54CA02342426B2FC003A5E04 /* ObserverFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserverFactory.swift; sourceTree = "<group>"; };
|
54CA02342426B2FC003A5E04 /* ObserverFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserverFactory.swift; sourceTree = "<group>"; };
|
||||||
54CA02372426B2FC003A5E04 /* HTTPAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPAdapter.swift; sourceTree = "<group>"; };
|
|
||||||
54CA02382426B2FC003A5E04 /* SecureHTTPAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecureHTTPAdapter.swift; sourceTree = "<group>"; };
|
|
||||||
54CA023A2426B2FC003A5E04 /* AdapterSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdapterSocket.swift; sourceTree = "<group>"; };
|
54CA023A2426B2FC003A5E04 /* AdapterSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdapterSocket.swift; sourceTree = "<group>"; };
|
||||||
54CA023B2426B2FC003A5E04 /* DirectAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectAdapter.swift; sourceTree = "<group>"; };
|
54CA023B2426B2FC003A5E04 /* DirectAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectAdapter.swift; sourceTree = "<group>"; };
|
||||||
54CA023C2426B2FC003A5E04 /* SOCKS5Adapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SOCKS5Adapter.swift; sourceTree = "<group>"; };
|
|
||||||
54CA023D2426B2FC003A5E04 /* RejectAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RejectAdapter.swift; sourceTree = "<group>"; };
|
|
||||||
54CA02412426B2FC003A5E04 /* AuthenticationServerAdapterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationServerAdapterFactory.swift; sourceTree = "<group>"; };
|
|
||||||
54CA02422426B2FC003A5E04 /* RejectAdapterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RejectAdapterFactory.swift; sourceTree = "<group>"; };
|
|
||||||
54CA02432426B2FD003A5E04 /* AdapterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdapterFactory.swift; sourceTree = "<group>"; };
|
54CA02432426B2FD003A5E04 /* AdapterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdapterFactory.swift; sourceTree = "<group>"; };
|
||||||
54CA02442426B2FD003A5E04 /* SOCKS5AdapterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SOCKS5AdapterFactory.swift; sourceTree = "<group>"; };
|
|
||||||
54CA02452426B2FD003A5E04 /* SecureHTTPAdapterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecureHTTPAdapterFactory.swift; sourceTree = "<group>"; };
|
|
||||||
54CA02462426B2FD003A5E04 /* ServerAdapterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerAdapterFactory.swift; sourceTree = "<group>"; };
|
|
||||||
54CA02472426B2FD003A5E04 /* AdapterFactoryManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdapterFactoryManager.swift; sourceTree = "<group>"; };
|
|
||||||
54CA02482426B2FD003A5E04 /* HTTPAdapterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPAdapterFactory.swift; sourceTree = "<group>"; };
|
|
||||||
54CA024F2426B2FD003A5E04 /* HTTPProxySocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPProxySocket.swift; sourceTree = "<group>"; };
|
54CA024F2426B2FD003A5E04 /* HTTPProxySocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPProxySocket.swift; sourceTree = "<group>"; };
|
||||||
54CA02502426B2FD003A5E04 /* DirectProxySocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectProxySocket.swift; sourceTree = "<group>"; };
|
|
||||||
54CA02512426B2FD003A5E04 /* ProxySocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProxySocket.swift; sourceTree = "<group>"; };
|
54CA02512426B2FD003A5E04 /* ProxySocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProxySocket.swift; sourceTree = "<group>"; };
|
||||||
54CA02522426B2FD003A5E04 /* SOCKS5ProxySocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SOCKS5ProxySocket.swift; sourceTree = "<group>"; };
|
|
||||||
54CA02532426B2FD003A5E04 /* SocketProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketProtocol.swift; sourceTree = "<group>"; };
|
54CA02532426B2FD003A5E04 /* SocketProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketProtocol.swift; sourceTree = "<group>"; };
|
||||||
54CA02BD2426D4F3003A5E04 /* DDLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DDLog.swift; sourceTree = "<group>"; };
|
54CA02BD2426D4F3003A5E04 /* DDLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DDLog.swift; sourceTree = "<group>"; };
|
||||||
54CA02BF2426DCCC003A5E04 /* GCDAsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncSocket.m; sourceTree = "<group>"; };
|
54CA02BF2426DCCC003A5E04 /* GCDAsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncSocket.m; sourceTree = "<group>"; };
|
||||||
54CA02C02426DCCD003A5E04 /* GCDAsyncUdpSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncUdpSocket.m; sourceTree = "<group>"; };
|
54CA02C02426DCCD003A5E04 /* GCDAsyncUdpSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncUdpSocket.m; sourceTree = "<group>"; };
|
||||||
54CA02C12426DCCD003A5E04 /* GCDAsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDAsyncSocket.h; sourceTree = "<group>"; };
|
54CA02C12426DCCD003A5E04 /* GCDAsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDAsyncSocket.h; sourceTree = "<group>"; };
|
||||||
54CA02C22426DCCD003A5E04 /* GCDAsyncUdpSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDAsyncUdpSocket.h; sourceTree = "<group>"; };
|
54CA02C22426DCCD003A5E04 /* GCDAsyncUdpSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDAsyncUdpSocket.h; sourceTree = "<group>"; };
|
||||||
|
54CE8BC324B1ED2100CC1756 /* PushNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotification.swift; sourceTree = "<group>"; };
|
||||||
|
54CFE86724E3F401001687DD /* TVCShareRecording.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCShareRecording.swift; sourceTree = "<group>"; };
|
||||||
54D8B979246C9F2000EB2414 /* FilterPipeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterPipeline.swift; sourceTree = "<group>"; };
|
54D8B979246C9F2000EB2414 /* FilterPipeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterPipeline.swift; sourceTree = "<group>"; };
|
||||||
54D8B97B2471A7E000EB2414 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
|
54D8B97B2471A7E000EB2414 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
|
||||||
54D8B97D2471B88900EB2414 /* DBCommon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBCommon.swift; sourceTree = "<group>"; };
|
54D8B97D2471B88900EB2414 /* DBCommon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBCommon.swift; sourceTree = "<group>"; };
|
||||||
54D8B9822471BD8100EB2414 /* DBAppOnly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBAppOnly.swift; sourceTree = "<group>"; };
|
54D8B9822471BD8100EB2414 /* DBAppOnly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBAppOnly.swift; sourceTree = "<group>"; };
|
||||||
54D8B98524796E9800EB2414 /* GroupedDomainDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupedDomainDataSource.swift; sourceTree = "<group>"; };
|
54D8B98524796E9800EB2414 /* GroupedDomainDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupedDomainDataSource.swift; sourceTree = "<group>"; };
|
||||||
54E540F1247C423200F7C34A /* DomainFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainFilter.swift; sourceTree = "<group>"; };
|
54E540F1247C423200F7C34A /* DomainFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainFilter.swift; sourceTree = "<group>"; };
|
||||||
54E540F3247D3F2600F7C34A /* TestDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestDataSource.swift; sourceTree = "<group>"; };
|
54E540F3247D3F2600F7C34A /* SimulatorVPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorVPN.swift; sourceTree = "<group>"; };
|
||||||
54E540F7247DB90F00F7C34A /* RecordingsDB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordingsDB.swift; sourceTree = "<group>"; };
|
54E540F7247DB90F00F7C34A /* RecordingsDB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordingsDB.swift; sourceTree = "<group>"; };
|
||||||
54E540F92482414800F7C34A /* SyncUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncUpdate.swift; sourceTree = "<group>"; };
|
54E540F92482414800F7C34A /* SyncUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncUpdate.swift; sourceTree = "<group>"; };
|
||||||
54EFA4E5248EEE240022D618 /* DatePickerAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerAlert.swift; sourceTree = "<group>"; };
|
54E67E4524A8B0FE0025D261 /* PrefsShared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsShared.swift; sourceTree = "<group>"; };
|
||||||
|
54E67E4824A8B1280025D261 /* Prefs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Prefs.swift; sourceTree = "<group>"; };
|
||||||
|
54E67E4A24A8C6370025D261 /* GlassVPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlassVPN.swift; sourceTree = "<group>"; };
|
||||||
|
54E67E4E24A8E2910025D261 /* Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Equatable.swift; sourceTree = "<group>"; };
|
||||||
|
54E67E5024A8E8820025D261 /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = "<group>"; };
|
||||||
54EFA4E72491A16A0022D618 /* Font.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = "<group>"; };
|
54EFA4E72491A16A0022D618 /* Font.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
@@ -332,7 +389,8 @@
|
|||||||
5412F8ED24571B8100A63D7A /* VCDateFilter.swift */,
|
5412F8ED24571B8100A63D7A /* VCDateFilter.swift */,
|
||||||
54953E6023E0D69A0054345C /* TVCHosts.swift */,
|
54953E6023E0D69A0054345C /* TVCHosts.swift */,
|
||||||
54953E6E23E44CD00054345C /* TVCHostDetails.swift */,
|
54953E6E23E44CD00054345C /* TVCHostDetails.swift */,
|
||||||
541FC47424A12CE9009154D8 /* Analytics */,
|
544F911F24A67EC5001D4B00 /* TVCOccurrenceContext.swift */,
|
||||||
|
541FC47424A12CE9009154D8 /* Analysis */,
|
||||||
);
|
);
|
||||||
path = Requests;
|
path = Requests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -342,6 +400,9 @@
|
|||||||
children = (
|
children = (
|
||||||
542E2A9924051556001462DC /* TVCSettings.swift */,
|
542E2A9924051556001462DC /* TVCSettings.swift */,
|
||||||
54B34593240E6343004C53CC /* TVCFilter.swift */,
|
54B34593240E6343004C53CC /* TVCFilter.swift */,
|
||||||
|
5412FCBF24C628F9000DE429 /* TVCReminderAlerts.swift */,
|
||||||
|
5412FCC124C628FA000DE429 /* TVCConnectionAlerts.swift */,
|
||||||
|
5412FCC024C628F9000DE429 /* TVCChooseAlertTone.swift */,
|
||||||
);
|
);
|
||||||
path = Settings;
|
path = Settings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -351,12 +412,27 @@
|
|||||||
children = (
|
children = (
|
||||||
540E677F242D2CF100871BBE /* VCRecordings.swift */,
|
540E677F242D2CF100871BBE /* VCRecordings.swift */,
|
||||||
540E67832433FAFE00871BBE /* TVCPreviousRecords.swift */,
|
540E67832433FAFE00871BBE /* TVCPreviousRecords.swift */,
|
||||||
540E67812433483D00871BBE /* VCEditRecording.swift */,
|
|
||||||
5458EBBF243A3F2200CFEB15 /* TVCRecordingDetails.swift */,
|
5458EBBF243A3F2200CFEB15 /* TVCRecordingDetails.swift */,
|
||||||
|
54CFE86724E3F401001687DD /* TVCShareRecording.swift */,
|
||||||
|
549A96D52501198400C565FA /* VCEditText.swift */,
|
||||||
|
540E67812433483D00871BBE /* VCEditRecording.swift */,
|
||||||
|
549D6ED424D5BFDB0032E498 /* TVCAppSearch.swift */,
|
||||||
|
54B345B12422E029004C53CC /* App Icons */,
|
||||||
);
|
);
|
||||||
path = Recordings;
|
path = Recordings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
541075D324CE284700D6F1BF /* Push Notifications */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
541075CD24C9D43A00D6F1BF /* UNNotification.swift */,
|
||||||
|
54CE8BC324B1ED2100CC1756 /* PushNotification.swift */,
|
||||||
|
543078C824B75CD100278F2D /* PushNotificationAppOnly.swift */,
|
||||||
|
541075D424CE286200D6F1BF /* CachedConnectionAlert.swift */,
|
||||||
|
);
|
||||||
|
path = "Push Notifications";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
541AC5CB2399498A00A769D7 = {
|
541AC5CB2399498A00A769D7 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -383,15 +459,16 @@
|
|||||||
54E540F0247C386500F7C34A /* Data Source */,
|
54E540F0247C386500F7C34A /* Data Source */,
|
||||||
54B345A4241BB975004C53CC /* Extensions */,
|
54B345A4241BB975004C53CC /* Extensions */,
|
||||||
545DDDD224436A03003B6544 /* Common Classes */,
|
545DDDD224436A03003B6544 /* Common Classes */,
|
||||||
|
541075D324CE284700D6F1BF /* Push Notifications */,
|
||||||
548B1F9423D338EC005B047C /* main.entitlements */,
|
548B1F9423D338EC005B047C /* main.entitlements */,
|
||||||
541AC5D72399498A00A769D7 /* AppDelegate.swift */,
|
541AC5D72399498A00A769D7 /* AppDelegate.swift */,
|
||||||
|
54E67E4A24A8C6370025D261 /* GlassVPN.swift */,
|
||||||
|
541075D824CE2C7200D6F1BF /* GlassVPNHook.swift */,
|
||||||
542E2A972404973F001462DC /* TBCMain.swift */,
|
542E2A972404973F001462DC /* TBCMain.swift */,
|
||||||
540C6454240D5BAE00E948F9 /* Requests */,
|
540C6454240D5BAE00E948F9 /* Requests */,
|
||||||
540E677E242D2CD200871BBE /* Recordings */,
|
540E677E242D2CD200871BBE /* Recordings */,
|
||||||
540C6455240D5BD200E948F9 /* Settings */,
|
540C6455240D5BD200E948F9 /* Settings */,
|
||||||
54B345B12422E029004C53CC /* unused */,
|
54A0CC0D24E314B6009B5EC1 /* GUI */,
|
||||||
541AC5E02399498B00A769D7 /* LaunchScreen.storyboard */,
|
|
||||||
541AC5DB2399498A00A769D7 /* Main.storyboard */,
|
|
||||||
541AC5DE2399498B00A769D7 /* Assets.xcassets */,
|
541AC5DE2399498B00A769D7 /* Assets.xcassets */,
|
||||||
541AC5E32399498B00A769D7 /* Info.plist */,
|
541AC5E32399498B00A769D7 /* Info.plist */,
|
||||||
54953E7023E473F10054345C /* Settings.bundle */,
|
54953E7023E473F10054345C /* Settings.bundle */,
|
||||||
@@ -399,23 +476,44 @@
|
|||||||
path = main;
|
path = main;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
541FC47424A12CE9009154D8 /* Analytics */ = {
|
541FC47424A12CE9009154D8 /* Analysis */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
5404AEEE24ACC089003B2F54 /* VCAnalysisBar.swift */,
|
||||||
541FC47724A1453F009154D8 /* VCCoOccurrence.swift */,
|
541FC47724A1453F009154D8 /* VCCoOccurrence.swift */,
|
||||||
);
|
);
|
||||||
path = Analytics;
|
path = Analysis;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
542E2A9B24051F79001462DC /* media */ = {
|
542E2A9B24051F79001462DC /* media */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
54686A8324FD0A3F0084934D /* tutorials */,
|
||||||
|
5430789E24B5E10E00278F2D /* sounds */,
|
||||||
541A957523E602DF00C09C19 /* LaunchIcon.png */,
|
541A957523E602DF00C09C19 /* LaunchIcon.png */,
|
||||||
54B345AF242264F8004C53CC /* third-level.txt */,
|
54B345AF242264F8004C53CC /* third-level.txt */,
|
||||||
);
|
);
|
||||||
path = media;
|
path = media;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
5430789E24B5E10E00278F2D /* sounds */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
543078A824B5E12400278F2D /* clock.caf */,
|
||||||
|
543078A524B5E12300278F2D /* drum1.caf */,
|
||||||
|
543078A924B5E12500278F2D /* drum2.caf */,
|
||||||
|
543078A324B5E12300278F2D /* plop1.caf */,
|
||||||
|
543078A224B5E12300278F2D /* plop2.caf */,
|
||||||
|
543078A424B5E12300278F2D /* snap1.caf */,
|
||||||
|
5430789F24B5E12200278F2D /* snap2.caf */,
|
||||||
|
543078A724B5E12400278F2D /* typewriter1.caf */,
|
||||||
|
543078A024B5E12200278F2D /* typewriter2.caf */,
|
||||||
|
543078A124B5E12300278F2D /* wood1.caf */,
|
||||||
|
543078A624B5E12400278F2D /* wood2.caf */,
|
||||||
|
);
|
||||||
|
path = sounds;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
543CDB1E23EEE61900B7F323 /* GlassVPN */ = {
|
543CDB1E23EEE61900B7F323 /* GlassVPN */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -433,16 +531,48 @@
|
|||||||
545DDDD224436A03003B6544 /* Common Classes */ = {
|
545DDDD224436A03003B6544 /* Common Classes */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
54E67E4824A8B1280025D261 /* Prefs.swift */,
|
||||||
|
54E67E4524A8B0FE0025D261 /* PrefsShared.swift */,
|
||||||
545DDDD024436983003B6544 /* QuickUI.swift */,
|
545DDDD024436983003B6544 /* QuickUI.swift */,
|
||||||
545DDDCE243E6267003B6544 /* TutorialSheet.swift */,
|
545DDDCE243E6267003B6544 /* TutorialSheet.swift */,
|
||||||
|
54686A8624FD26410084934D /* TinyMarkdown.swift */,
|
||||||
54D8B979246C9F2000EB2414 /* FilterPipeline.swift */,
|
54D8B979246C9F2000EB2414 /* FilterPipeline.swift */,
|
||||||
54448A3124899A4000771C96 /* SearchBarManager.swift */,
|
54448A3124899A4000771C96 /* SearchBarManager.swift */,
|
||||||
54EFA4E5248EEE240022D618 /* DatePickerAlert.swift */,
|
549ECD9C24A7AD550097571C /* CustomAlert.swift */,
|
||||||
541FC47524A12D01009154D8 /* IBViews.swift */,
|
541FC47524A12D01009154D8 /* IBViews.swift */,
|
||||||
|
5404AEEC24A95F3F003B2F54 /* SlideInAnimation.swift */,
|
||||||
|
541075D024CDBA0000D6F1BF /* ThrottledBatchQueue.swift */,
|
||||||
|
54686A7524F8062C0084934D /* NotificationBanner.swift */,
|
||||||
);
|
);
|
||||||
path = "Common Classes";
|
path = "Common Classes";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
54686A8324FD0A3F0084934D /* tutorials */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
54686A8824FD31580084934D /* tut-welcome-1.md */,
|
||||||
|
54686A8B24FD3F180084934D /* tut-welcome-2.md */,
|
||||||
|
54686A8A24FD3F100084934D /* tut-recording-1.md */,
|
||||||
|
54686A8924FD31630084934D /* tut-recording-2.md */,
|
||||||
|
54686A8424FD0A3F0084934D /* tut-recording-howto.md */,
|
||||||
|
54686A8C24FD3F630084934D /* tut-cooccurrence.md */,
|
||||||
|
);
|
||||||
|
path = tutorials;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
54A0CC0D24E314B6009B5EC1 /* GUI */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
541AC5E02399498B00A769D7 /* LaunchScreen.storyboard */,
|
||||||
|
541AC5DB2399498A00A769D7 /* Main.storyboard */,
|
||||||
|
54A0CC0A24E30D6F009B5EC1 /* Requests.storyboard */,
|
||||||
|
549A96D8250419B200C565FA /* CoOccurrence.storyboard */,
|
||||||
|
54A0CC0724E30C56009B5EC1 /* Recordings.storyboard */,
|
||||||
|
543078C124B60F3B00278F2D /* Settings.storyboard */,
|
||||||
|
);
|
||||||
|
path = GUI;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
54B3459A2415651C004C53CC /* DB */ = {
|
54B3459A2415651C004C53CC /* DB */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -458,10 +588,12 @@
|
|||||||
54B345A4241BB975004C53CC /* Extensions */ = {
|
54B345A4241BB975004C53CC /* Extensions */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
544C95252407B1C700AB89D0 /* SharedState.swift */,
|
54B345A8241BBA0B004C53CC /* Logging.swift */,
|
||||||
54B345A8241BBA0B004C53CC /* Generic.swift */,
|
54E67E4E24A8E2910025D261 /* Equatable.swift */,
|
||||||
54B345A5241BB982004C53CC /* Notifications.swift */,
|
54B345A5241BB982004C53CC /* Notifications.swift */,
|
||||||
54B345AA241BBA5B004C53CC /* AlertSheet.swift */,
|
54B345AA241BBA5B004C53CC /* AlertSheet.swift */,
|
||||||
|
54E67E5024A8E8820025D261 /* View.swift */,
|
||||||
|
541DCA6024A6B0F6005F1A4B /* Color.swift */,
|
||||||
54448A2F248647D900771C96 /* Time.swift */,
|
54448A2F248647D900771C96 /* Time.swift */,
|
||||||
54751E502423955000168273 /* URL.swift */,
|
54751E502423955000168273 /* URL.swift */,
|
||||||
54EFA4E72491A16A0022D618 /* Font.swift */,
|
54EFA4E72491A16A0022D618 /* Font.swift */,
|
||||||
@@ -473,13 +605,13 @@
|
|||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
54B345B12422E029004C53CC /* unused */ = {
|
54B345B12422E029004C53CC /* App Icons */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
54C056DC23E9EEF700214A3F /* BundleIcon.swift */,
|
54C056DC23E9EEF700214A3F /* BundleIcon.swift */,
|
||||||
54C056DA23E9E36E00214A3F /* AppInfoType.swift */,
|
54C056DA23E9E36E00214A3F /* AppStoreSearch.swift */,
|
||||||
);
|
);
|
||||||
path = unused;
|
path = "App Icons";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
54CA00D52426A7F2003A5E04 /* robbiehanson-CocoaAsyncSocket */ = {
|
54CA00D52426A7F2003A5E04 /* robbiehanson-CocoaAsyncSocket */ = {
|
||||||
@@ -507,7 +639,6 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
54CA01E12426B2FC003A5E04 /* Messages */,
|
54CA01E12426B2FC003A5E04 /* Messages */,
|
||||||
54CA01E42426B2FC003A5E04 /* ResponseGeneratorFactory.swift */,
|
|
||||||
54CA01E52426B2FC003A5E04 /* ProxyServer */,
|
54CA01E52426B2FC003A5E04 /* ProxyServer */,
|
||||||
54CA01EE2426B2FC003A5E04 /* RawSocket */,
|
54CA01EE2426B2FC003A5E04 /* RawSocket */,
|
||||||
54CA01F72426B2FC003A5E04 /* Opt.swift */,
|
54CA01F72426B2FC003A5E04 /* Opt.swift */,
|
||||||
@@ -538,7 +669,6 @@
|
|||||||
children = (
|
children = (
|
||||||
54CA01E62426B2FC003A5E04 /* ProxyServer.swift */,
|
54CA01E62426B2FC003A5E04 /* ProxyServer.swift */,
|
||||||
54CA01E72426B2FC003A5E04 /* GCDProxyServer.swift */,
|
54CA01E72426B2FC003A5E04 /* GCDProxyServer.swift */,
|
||||||
54CA01E82426B2FC003A5E04 /* GCDSOCKS5ProxyServer.swift */,
|
|
||||||
54CA01E92426B2FC003A5E04 /* GCDHTTPProxyServer.swift */,
|
54CA01E92426B2FC003A5E04 /* GCDHTTPProxyServer.swift */,
|
||||||
);
|
);
|
||||||
path = ProxyServer;
|
path = ProxyServer;
|
||||||
@@ -579,15 +709,8 @@
|
|||||||
54CA02092426B2FC003A5E04 /* Rule */ = {
|
54CA02092426B2FC003A5E04 /* Rule */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
54CA020A2426B2FC003A5E04 /* DomainListRule.swift */,
|
|
||||||
54CA020C2426B2FC003A5E04 /* DNSSessionMatchType.swift */,
|
54CA020C2426B2FC003A5E04 /* DNSSessionMatchType.swift */,
|
||||||
54CA020D2426B2FC003A5E04 /* DNSFailRule.swift */,
|
|
||||||
54CA020E2426B2FC003A5E04 /* AllRule.swift */,
|
|
||||||
54CA020F2426B2FC003A5E04 /* DNSSessionMatchResult.swift */,
|
54CA020F2426B2FC003A5E04 /* DNSSessionMatchResult.swift */,
|
||||||
54CA02102426B2FC003A5E04 /* Rule.swift */,
|
|
||||||
54CA02112426B2FC003A5E04 /* DirectRule.swift */,
|
|
||||||
54CA02122426B2FC003A5E04 /* RuleManager.swift */,
|
|
||||||
54CA02132426B2FC003A5E04 /* IPRangeListRule.swift */,
|
|
||||||
);
|
);
|
||||||
path = Rule;
|
path = Rule;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -650,7 +773,6 @@
|
|||||||
54CA02302426B2FC003A5E04 /* EventType.swift */,
|
54CA02302426B2FC003A5E04 /* EventType.swift */,
|
||||||
54CA02312426B2FC003A5E04 /* ProxySocketEvent.swift */,
|
54CA02312426B2FC003A5E04 /* ProxySocketEvent.swift */,
|
||||||
54CA02322426B2FC003A5E04 /* TunnelEvent.swift */,
|
54CA02322426B2FC003A5E04 /* TunnelEvent.swift */,
|
||||||
54CA02332426B2FC003A5E04 /* RuleMatchEvent.swift */,
|
|
||||||
);
|
);
|
||||||
path = Event;
|
path = Event;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -668,12 +790,8 @@
|
|||||||
54CA02362426B2FC003A5E04 /* AdapterSocket */ = {
|
54CA02362426B2FC003A5E04 /* AdapterSocket */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
54CA02372426B2FC003A5E04 /* HTTPAdapter.swift */,
|
|
||||||
54CA02382426B2FC003A5E04 /* SecureHTTPAdapter.swift */,
|
|
||||||
54CA023A2426B2FC003A5E04 /* AdapterSocket.swift */,
|
54CA023A2426B2FC003A5E04 /* AdapterSocket.swift */,
|
||||||
54CA023B2426B2FC003A5E04 /* DirectAdapter.swift */,
|
54CA023B2426B2FC003A5E04 /* DirectAdapter.swift */,
|
||||||
54CA023C2426B2FC003A5E04 /* SOCKS5Adapter.swift */,
|
|
||||||
54CA023D2426B2FC003A5E04 /* RejectAdapter.swift */,
|
|
||||||
54CA023E2426B2FC003A5E04 /* Factory */,
|
54CA023E2426B2FC003A5E04 /* Factory */,
|
||||||
);
|
);
|
||||||
path = AdapterSocket;
|
path = AdapterSocket;
|
||||||
@@ -682,14 +800,7 @@
|
|||||||
54CA023E2426B2FC003A5E04 /* Factory */ = {
|
54CA023E2426B2FC003A5E04 /* Factory */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
54CA02412426B2FC003A5E04 /* AuthenticationServerAdapterFactory.swift */,
|
|
||||||
54CA02422426B2FC003A5E04 /* RejectAdapterFactory.swift */,
|
|
||||||
54CA02432426B2FD003A5E04 /* AdapterFactory.swift */,
|
54CA02432426B2FD003A5E04 /* AdapterFactory.swift */,
|
||||||
54CA02442426B2FD003A5E04 /* SOCKS5AdapterFactory.swift */,
|
|
||||||
54CA02452426B2FD003A5E04 /* SecureHTTPAdapterFactory.swift */,
|
|
||||||
54CA02462426B2FD003A5E04 /* ServerAdapterFactory.swift */,
|
|
||||||
54CA02472426B2FD003A5E04 /* AdapterFactoryManager.swift */,
|
|
||||||
54CA02482426B2FD003A5E04 /* HTTPAdapterFactory.swift */,
|
|
||||||
);
|
);
|
||||||
path = Factory;
|
path = Factory;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -698,9 +809,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
54CA024F2426B2FD003A5E04 /* HTTPProxySocket.swift */,
|
54CA024F2426B2FD003A5E04 /* HTTPProxySocket.swift */,
|
||||||
54CA02502426B2FD003A5E04 /* DirectProxySocket.swift */,
|
|
||||||
54CA02512426B2FD003A5E04 /* ProxySocket.swift */,
|
54CA02512426B2FD003A5E04 /* ProxySocket.swift */,
|
||||||
54CA02522426B2FD003A5E04 /* SOCKS5ProxySocket.swift */,
|
|
||||||
);
|
);
|
||||||
path = ProxySocket;
|
path = ProxySocket;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -708,7 +817,7 @@
|
|||||||
54E540F0247C386500F7C34A /* Data Source */ = {
|
54E540F0247C386500F7C34A /* Data Source */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
54E540F3247D3F2600F7C34A /* TestDataSource.swift */,
|
54E540F3247D3F2600F7C34A /* SimulatorVPN.swift */,
|
||||||
54E540F92482414800F7C34A /* SyncUpdate.swift */,
|
54E540F92482414800F7C34A /* SyncUpdate.swift */,
|
||||||
54D8B98524796E9800EB2414 /* GroupedDomainDataSource.swift */,
|
54D8B98524796E9800EB2414 /* GroupedDomainDataSource.swift */,
|
||||||
54E540F1247C423200F7C34A /* DomainFilter.swift */,
|
54E540F1247C423200F7C34A /* DomainFilter.swift */,
|
||||||
@@ -767,7 +876,7 @@
|
|||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastSwiftUpdateCheck = 1130;
|
LastSwiftUpdateCheck = 1130;
|
||||||
LastUpgradeCheck = 1010;
|
LastUpgradeCheck = 1200;
|
||||||
ORGANIZATIONNAME = relikd;
|
ORGANIZATIONNAME = relikd;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
541AC5D32399498A00A769D7 = {
|
541AC5D32399498A00A769D7 = {
|
||||||
@@ -811,11 +920,32 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
543078AC24B5E12500278F2D /* typewriter2.caf in Resources */,
|
||||||
54953E7123E473F10054345C /* Settings.bundle in Resources */,
|
54953E7123E473F10054345C /* Settings.bundle in Resources */,
|
||||||
|
54686A9024FD42950084934D /* tut-recording-2.md in Resources */,
|
||||||
|
543078B024B5E12500278F2D /* plop2.caf in Resources */,
|
||||||
541AC5E22399498B00A769D7 /* LaunchScreen.storyboard in Resources */,
|
541AC5E22399498B00A769D7 /* LaunchScreen.storyboard in Resources */,
|
||||||
|
54A0CC0924E30C56009B5EC1 /* Recordings.storyboard in Resources */,
|
||||||
|
54686A8D24FD428C0084934D /* tut-welcome-1.md in Resources */,
|
||||||
|
543078B824B5E12500278F2D /* wood2.caf in Resources */,
|
||||||
|
543078BE24B5E12500278F2D /* drum2.caf in Resources */,
|
||||||
|
543078B424B5E12500278F2D /* snap1.caf in Resources */,
|
||||||
541AC5DF2399498B00A769D7 /* Assets.xcassets in Resources */,
|
541AC5DF2399498B00A769D7 /* Assets.xcassets in Resources */,
|
||||||
|
543078AE24B5E12500278F2D /* wood1.caf in Resources */,
|
||||||
|
54686A8524FD0A3F0084934D /* tut-recording-howto.md in Resources */,
|
||||||
541AC5DD2399498A00A769D7 /* Main.storyboard in Resources */,
|
541AC5DD2399498A00A769D7 /* Main.storyboard in Resources */,
|
||||||
54B345B0242264F8004C53CC /* third-level.txt in Resources */,
|
54B345B0242264F8004C53CC /* third-level.txt in Resources */,
|
||||||
|
54686A8F24FD42950084934D /* tut-recording-1.md in Resources */,
|
||||||
|
543078BA24B5E12500278F2D /* typewriter1.caf in Resources */,
|
||||||
|
543078B224B5E12500278F2D /* plop1.caf in Resources */,
|
||||||
|
543078B624B5E12500278F2D /* drum1.caf in Resources */,
|
||||||
|
543078BC24B5E12500278F2D /* clock.caf in Resources */,
|
||||||
|
543078C324B60F3B00278F2D /* Settings.storyboard in Resources */,
|
||||||
|
543078AA24B5E12500278F2D /* snap2.caf in Resources */,
|
||||||
|
54A0CC0C24E30D6F009B5EC1 /* Requests.storyboard in Resources */,
|
||||||
|
549A96DA250419B200C565FA /* CoOccurrence.storyboard in Resources */,
|
||||||
|
54686A9124FD42950084934D /* tut-cooccurrence.md in Resources */,
|
||||||
|
54686A8E24FD42950084934D /* tut-welcome-2.md in Resources */,
|
||||||
541A957623E602DF00C09C19 /* LaunchIcon.png in Resources */,
|
541A957623E602DF00C09C19 /* LaunchIcon.png in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -824,6 +954,17 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
543078AD24B5E12500278F2D /* typewriter2.caf in Resources */,
|
||||||
|
543078BB24B5E12500278F2D /* typewriter1.caf in Resources */,
|
||||||
|
543078BF24B5E12500278F2D /* drum2.caf in Resources */,
|
||||||
|
543078AF24B5E12500278F2D /* wood1.caf in Resources */,
|
||||||
|
543078B124B5E12500278F2D /* plop2.caf in Resources */,
|
||||||
|
543078AB24B5E12500278F2D /* snap2.caf in Resources */,
|
||||||
|
543078B924B5E12500278F2D /* wood2.caf in Resources */,
|
||||||
|
543078B724B5E12500278F2D /* drum1.caf in Resources */,
|
||||||
|
543078B524B5E12500278F2D /* snap1.caf in Resources */,
|
||||||
|
543078B324B5E12500278F2D /* plop1.caf in Resources */,
|
||||||
|
543078BD24B5E12500278F2D /* clock.caf in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -834,40 +975,59 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
54E67E4924A8B1280025D261 /* Prefs.swift in Sources */,
|
||||||
54E540F8247DB90F00F7C34A /* RecordingsDB.swift in Sources */,
|
54E540F8247DB90F00F7C34A /* RecordingsDB.swift in Sources */,
|
||||||
54E540F4247D3F2600F7C34A /* TestDataSource.swift in Sources */,
|
54E67E4F24A8E2910025D261 /* Equatable.swift in Sources */,
|
||||||
|
54E540F4247D3F2600F7C34A /* SimulatorVPN.swift in Sources */,
|
||||||
|
541075D924CE2C7200D6F1BF /* GlassVPNHook.swift in Sources */,
|
||||||
|
541075D524CE286200D6F1BF /* CachedConnectionAlert.swift in Sources */,
|
||||||
|
5404AEEF24ACC089003B2F54 /* VCAnalysisBar.swift in Sources */,
|
||||||
545DDDD424466D37003B6544 /* AutoLayout.swift in Sources */,
|
545DDDD424466D37003B6544 /* AutoLayout.swift in Sources */,
|
||||||
54B345AD241BBB00004C53CC /* DBExtensions.swift in Sources */,
|
54B345AD241BBB00004C53CC /* DBExtensions.swift in Sources */,
|
||||||
54E540F2247C423200F7C34A /* DomainFilter.swift in Sources */,
|
54E540F2247C423200F7C34A /* DomainFilter.swift in Sources */,
|
||||||
54D8B97E2471B88900EB2414 /* DBCommon.swift in Sources */,
|
54D8B97E2471B88900EB2414 /* DBCommon.swift in Sources */,
|
||||||
54D8B9832471BD8100EB2414 /* DBAppOnly.swift in Sources */,
|
54D8B9832471BD8100EB2414 /* DBAppOnly.swift in Sources */,
|
||||||
|
5412FCC224C628FA000DE429 /* TVCReminderAlerts.swift in Sources */,
|
||||||
|
54E67E4D24A8E20D0025D261 /* TVCSettings.swift in Sources */,
|
||||||
540E67842433FAFE00871BBE /* TVCPreviousRecords.swift in Sources */,
|
540E67842433FAFE00871BBE /* TVCPreviousRecords.swift in Sources */,
|
||||||
54B345A6241BB982004C53CC /* Notifications.swift in Sources */,
|
54B345A6241BB982004C53CC /* Notifications.swift in Sources */,
|
||||||
54448A2E2486464F00771C96 /* Array.swift in Sources */,
|
54448A2E2486464F00771C96 /* Array.swift in Sources */,
|
||||||
|
54E67E4B24A8C6370025D261 /* GlassVPN.swift in Sources */,
|
||||||
|
54686A7624F8062C0084934D /* NotificationBanner.swift in Sources */,
|
||||||
541FC47824A1453F009154D8 /* VCCoOccurrence.swift in Sources */,
|
541FC47824A1453F009154D8 /* VCCoOccurrence.swift in Sources */,
|
||||||
54B345AB241BBA5B004C53CC /* AlertSheet.swift in Sources */,
|
54B345AB241BBA5B004C53CC /* AlertSheet.swift in Sources */,
|
||||||
544C95262407B1C700AB89D0 /* SharedState.swift in Sources */,
|
541DCA6124A6B0F6005F1A4B /* Color.swift in Sources */,
|
||||||
545DDDCF243E6267003B6544 /* TutorialSheet.swift in Sources */,
|
545DDDCF243E6267003B6544 /* TutorialSheet.swift in Sources */,
|
||||||
54B345A9241BBA0B004C53CC /* Generic.swift in Sources */,
|
54B345A9241BBA0B004C53CC /* Logging.swift in Sources */,
|
||||||
54B34596240F0513004C53CC /* TableView.swift in Sources */,
|
54B34596240F0513004C53CC /* TableView.swift in Sources */,
|
||||||
540E6780242D2CF100871BBE /* VCRecordings.swift in Sources */,
|
540E6780242D2CF100871BBE /* VCRecordings.swift in Sources */,
|
||||||
|
541075D124CDBA0000D6F1BF /* ThrottledBatchQueue.swift in Sources */,
|
||||||
54953E3323DC752E0054345C /* DBCore.swift in Sources */,
|
54953E3323DC752E0054345C /* DBCore.swift in Sources */,
|
||||||
|
54E67E4624A8B0FE0025D261 /* PrefsShared.swift in Sources */,
|
||||||
|
5412FCC324C628FA000DE429 /* TVCChooseAlertTone.swift in Sources */,
|
||||||
|
544F912024A67EC5001D4B00 /* TVCOccurrenceContext.swift in Sources */,
|
||||||
54448A30248647D900771C96 /* Time.swift in Sources */,
|
54448A30248647D900771C96 /* Time.swift in Sources */,
|
||||||
54953E6123E0D69A0054345C /* TVCHosts.swift in Sources */,
|
54953E6123E0D69A0054345C /* TVCHosts.swift in Sources */,
|
||||||
|
549D6ED524D5BFDB0032E498 /* TVCAppSearch.swift in Sources */,
|
||||||
|
54CFE86824E3F401001687DD /* TVCShareRecording.swift in Sources */,
|
||||||
54751E512423955100168273 /* URL.swift in Sources */,
|
54751E512423955100168273 /* URL.swift in Sources */,
|
||||||
542E2A9A24051556001462DC /* TVCSettings.swift in Sources */,
|
|
||||||
54953E5F23DEBE840054345C /* TVCDomains.swift in Sources */,
|
54953E5F23DEBE840054345C /* TVCDomains.swift in Sources */,
|
||||||
54C056DB23E9E36E00214A3F /* AppInfoType.swift in Sources */,
|
54C056DB23E9E36E00214A3F /* AppStoreSearch.swift in Sources */,
|
||||||
54D8B98624796E9900EB2414 /* GroupedDomainDataSource.swift in Sources */,
|
54D8B98624796E9900EB2414 /* GroupedDomainDataSource.swift in Sources */,
|
||||||
|
541075CE24C9D43A00D6F1BF /* UNNotification.swift in Sources */,
|
||||||
54953E6F23E44CD00054345C /* TVCHostDetails.swift in Sources */,
|
54953E6F23E44CD00054345C /* TVCHostDetails.swift in Sources */,
|
||||||
54D8B97C2471A7E000EB2414 /* String.swift in Sources */,
|
54D8B97C2471A7E000EB2414 /* String.swift in Sources */,
|
||||||
54EFA4E6248EEE240022D618 /* DatePickerAlert.swift in Sources */,
|
54E67E5124A8E8820025D261 /* View.swift in Sources */,
|
||||||
54C056DD23E9EEF700214A3F /* BundleIcon.swift in Sources */,
|
54C056DD23E9EEF700214A3F /* BundleIcon.swift in Sources */,
|
||||||
542E2A982404973F001462DC /* TBCMain.swift in Sources */,
|
542E2A982404973F001462DC /* TBCMain.swift in Sources */,
|
||||||
54448A3224899A4000771C96 /* SearchBarManager.swift in Sources */,
|
54448A3224899A4000771C96 /* SearchBarManager.swift in Sources */,
|
||||||
5458EBC0243A3F2200CFEB15 /* TVCRecordingDetails.swift in Sources */,
|
5458EBC0243A3F2200CFEB15 /* TVCRecordingDetails.swift in Sources */,
|
||||||
|
549A96D62501198400C565FA /* VCEditText.swift in Sources */,
|
||||||
541FC9872497D81C00962623 /* TheGreatDestroyer.swift in Sources */,
|
541FC9872497D81C00962623 /* TheGreatDestroyer.swift in Sources */,
|
||||||
|
54686A8724FD27AA0084934D /* TinyMarkdown.swift in Sources */,
|
||||||
5412F8EE24571B8200A63D7A /* VCDateFilter.swift in Sources */,
|
5412F8EE24571B8200A63D7A /* VCDateFilter.swift in Sources */,
|
||||||
|
5412FCC424C628FA000DE429 /* TVCConnectionAlerts.swift in Sources */,
|
||||||
|
543078C924B75CEA00278F2D /* PushNotificationAppOnly.swift in Sources */,
|
||||||
545DDDD124436983003B6544 /* QuickUI.swift in Sources */,
|
545DDDD124436983003B6544 /* QuickUI.swift in Sources */,
|
||||||
541AC5D82399498A00A769D7 /* AppDelegate.swift in Sources */,
|
541AC5D82399498A00A769D7 /* AppDelegate.swift in Sources */,
|
||||||
541FC47624A12D01009154D8 /* IBViews.swift in Sources */,
|
541FC47624A12D01009154D8 /* IBViews.swift in Sources */,
|
||||||
@@ -875,7 +1035,10 @@
|
|||||||
54EFA4E82491A16A0022D618 /* Font.swift in Sources */,
|
54EFA4E82491A16A0022D618 /* Font.swift in Sources */,
|
||||||
54D8B97A246C9F2000EB2414 /* FilterPipeline.swift in Sources */,
|
54D8B97A246C9F2000EB2414 /* FilterPipeline.swift in Sources */,
|
||||||
54B34594240E6343004C53CC /* TVCFilter.swift in Sources */,
|
54B34594240E6343004C53CC /* TVCFilter.swift in Sources */,
|
||||||
|
549ECD9D24A7AD550097571C /* CustomAlert.swift in Sources */,
|
||||||
|
54CE8BC424B1ED2100CC1756 /* PushNotification.swift in Sources */,
|
||||||
54E540FA2482414800F7C34A /* SyncUpdate.swift in Sources */,
|
54E540FA2482414800F7C34A /* SyncUpdate.swift in Sources */,
|
||||||
|
5404AEED24A95F3F003B2F54 /* SlideInAnimation.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -886,36 +1049,31 @@
|
|||||||
54CA027A2426B2FD003A5E04 /* HTTPURL.swift in Sources */,
|
54CA027A2426B2FD003A5E04 /* HTTPURL.swift in Sources */,
|
||||||
54CA025D2426B2FD003A5E04 /* HTTPHeader.swift in Sources */,
|
54CA025D2426B2FD003A5E04 /* HTTPHeader.swift in Sources */,
|
||||||
54CA02832426B2FD003A5E04 /* DNSSessionMatchResult.swift in Sources */,
|
54CA02832426B2FD003A5E04 /* DNSSessionMatchResult.swift in Sources */,
|
||||||
54CA02862426B2FD003A5E04 /* RuleManager.swift in Sources */,
|
|
||||||
54CA02B82426B2FD003A5E04 /* HTTPProxySocket.swift in Sources */,
|
54CA02B82426B2FD003A5E04 /* HTTPProxySocket.swift in Sources */,
|
||||||
54CA02C32426DCCD003A5E04 /* GCDAsyncSocket.m in Sources */,
|
54CA02C32426DCCD003A5E04 /* GCDAsyncSocket.m in Sources */,
|
||||||
54CA02752426B2FD003A5E04 /* IPRange.swift in Sources */,
|
54CA02752426B2FD003A5E04 /* IPRange.swift in Sources */,
|
||||||
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 */,
|
|
||||||
54CA02892426B2FD003A5E04 /* Tunnel.swift in Sources */,
|
54CA02892426B2FD003A5E04 /* Tunnel.swift in Sources */,
|
||||||
54CA029F2426B2FD003A5E04 /* ProxySocketEvent.swift in Sources */,
|
54CA029F2426B2FD003A5E04 /* ProxySocketEvent.swift in Sources */,
|
||||||
54CA027D2426B2FD003A5E04 /* GlobalIntializer.swift in Sources */,
|
54CA027D2426B2FD003A5E04 /* GlobalIntializer.swift in Sources */,
|
||||||
54CA026F2426B2FD003A5E04 /* Port.swift in Sources */,
|
54CA026F2426B2FD003A5E04 /* Port.swift in Sources */,
|
||||||
54CA028A2426B2FD003A5E04 /* ResponseGenerator.swift in Sources */,
|
54CA028A2426B2FD003A5E04 /* ResponseGenerator.swift in Sources */,
|
||||||
54CA027C2426B2FD003A5E04 /* StreamScanner.swift in Sources */,
|
54CA027C2426B2FD003A5E04 /* StreamScanner.swift in Sources */,
|
||||||
54CA02AF2426B2FD003A5E04 /* SOCKS5AdapterFactory.swift in Sources */,
|
|
||||||
54CA029E2426B2FD003A5E04 /* EventType.swift in Sources */,
|
54CA029E2426B2FD003A5E04 /* EventType.swift in Sources */,
|
||||||
54CA02912426B2FD003A5E04 /* DNSMessage.swift in Sources */,
|
54CA02912426B2FD003A5E04 /* DNSMessage.swift in Sources */,
|
||||||
54CA02712426B2FD003A5E04 /* UInt128.swift in Sources */,
|
54CA02712426B2FD003A5E04 /* UInt128.swift in Sources */,
|
||||||
54CA02882426B2FD003A5E04 /* QueueFactory.swift in Sources */,
|
54CA02882426B2FD003A5E04 /* QueueFactory.swift in Sources */,
|
||||||
54CA02A12426B2FD003A5E04 /* RuleMatchEvent.swift in Sources */,
|
|
||||||
54CA02BE2426D4F3003A5E04 /* DDLog.swift in Sources */,
|
54CA02BE2426D4F3003A5E04 /* DDLog.swift in Sources */,
|
||||||
54CA02962426B2FD003A5E04 /* PacketProtocolParser.swift in Sources */,
|
54CA02962426B2FD003A5E04 /* PacketProtocolParser.swift in Sources */,
|
||||||
54CA02932426B2FD003A5E04 /* DNSServer.swift in Sources */,
|
54CA02932426B2FD003A5E04 /* DNSServer.swift in Sources */,
|
||||||
54CA02B22426B2FD003A5E04 /* AdapterFactoryManager.swift in Sources */,
|
|
||||||
54CA02AE2426B2FD003A5E04 /* AdapterFactory.swift in Sources */,
|
54CA02AE2426B2FD003A5E04 /* AdapterFactory.swift in Sources */,
|
||||||
54CA02A82426B2FD003A5E04 /* SOCKS5Adapter.swift in Sources */,
|
|
||||||
54CA02792426B2FD003A5E04 /* Checksum.swift in Sources */,
|
54CA02792426B2FD003A5E04 /* Checksum.swift in Sources */,
|
||||||
54CA02AD2426B2FD003A5E04 /* RejectAdapterFactory.swift in Sources */,
|
541075D224CDBA0000D6F1BF /* ThrottledBatchQueue.swift in Sources */,
|
||||||
54CA02672426B2FD003A5E04 /* RawTCPSocketProtocol.swift in Sources */,
|
54CA02672426B2FD003A5E04 /* RawTCPSocketProtocol.swift in Sources */,
|
||||||
54CA02602426B2FD003A5E04 /* GCDProxyServer.swift in Sources */,
|
54CA02602426B2FD003A5E04 /* GCDProxyServer.swift in Sources */,
|
||||||
54CA026B2426B2FD003A5E04 /* GCDTCPSocket.swift in Sources */,
|
54CA026B2426B2FD003A5E04 /* GCDTCPSocket.swift in Sources */,
|
||||||
@@ -923,50 +1081,38 @@
|
|||||||
54CA01D52426B252003A5E04 /* SafeDict.swift in Sources */,
|
54CA01D52426B252003A5E04 /* SafeDict.swift in Sources */,
|
||||||
54CA027B2426B2FD003A5E04 /* HTTPAuthentication.swift in Sources */,
|
54CA027B2426B2FD003A5E04 /* HTTPAuthentication.swift in Sources */,
|
||||||
54CA02762426B2FD003A5E04 /* IPAddress.swift in Sources */,
|
54CA02762426B2FD003A5E04 /* IPAddress.swift in Sources */,
|
||||||
54CA02B02426B2FD003A5E04 /* SecureHTTPAdapterFactory.swift in Sources */,
|
|
||||||
54CA02A62426B2FD003A5E04 /* AdapterSocket.swift in Sources */,
|
54CA02A62426B2FD003A5E04 /* AdapterSocket.swift in Sources */,
|
||||||
54CA02742426B2FD003A5E04 /* IPMask.swift in Sources */,
|
54CA02742426B2FD003A5E04 /* IPMask.swift in Sources */,
|
||||||
54CA02BB2426B2FD003A5E04 /* SOCKS5ProxySocket.swift in Sources */,
|
541075CF24C9D43A00D6F1BF /* UNNotification.swift in Sources */,
|
||||||
54D8B97F2471B89100EB2414 /* DBCommon.swift in Sources */,
|
54D8B97F2471B89100EB2414 /* DBCommon.swift in Sources */,
|
||||||
54CA02A42426B2FD003A5E04 /* SecureHTTPAdapter.swift in Sources */,
|
|
||||||
54CA02942426B2FD003A5E04 /* DNSResolver.swift in Sources */,
|
54CA02942426B2FD003A5E04 /* DNSResolver.swift in Sources */,
|
||||||
54CA025F2426B2FD003A5E04 /* ProxyServer.swift in Sources */,
|
54CA025F2426B2FD003A5E04 /* ProxyServer.swift in Sources */,
|
||||||
54CA02842426B2FD003A5E04 /* Rule.swift in Sources */,
|
54CE8BC524B1ED2100CC1756 /* PushNotification.swift in Sources */,
|
||||||
54CA02B92426B2FD003A5E04 /* DirectProxySocket.swift in Sources */,
|
|
||||||
54751E522423955100168273 /* URL.swift in Sources */,
|
54751E522423955100168273 /* URL.swift in Sources */,
|
||||||
54CA02A92426B2FD003A5E04 /* RejectAdapter.swift in Sources */,
|
|
||||||
54CA02732426B2FD003A5E04 /* IPPool.swift in Sources */,
|
54CA02732426B2FD003A5E04 /* IPPool.swift in Sources */,
|
||||||
54CA027E2426B2FD003A5E04 /* DomainListRule.swift in Sources */,
|
541075D624CE286200D6F1BF /* CachedConnectionAlert.swift in Sources */,
|
||||||
54CA02782426B2FD003A5E04 /* BinaryDataScanner.swift in Sources */,
|
54CA02782426B2FD003A5E04 /* BinaryDataScanner.swift in Sources */,
|
||||||
54CA02B12426B2FD003A5E04 /* ServerAdapterFactory.swift in Sources */,
|
|
||||||
54CA02952426B2FD003A5E04 /* DNSEnums.swift in Sources */,
|
54CA02952426B2FD003A5E04 /* DNSEnums.swift in Sources */,
|
||||||
54CA02802426B2FD003A5E04 /* DNSSessionMatchType.swift in Sources */,
|
54CA02802426B2FD003A5E04 /* DNSSessionMatchType.swift in Sources */,
|
||||||
54CA02A22426B2FD003A5E04 /* ObserverFactory.swift in Sources */,
|
54CA02A22426B2FD003A5E04 /* ObserverFactory.swift in Sources */,
|
||||||
54CA02612426B2FD003A5E04 /* GCDSOCKS5ProxyServer.swift in Sources */,
|
|
||||||
54CA029D2426B2FD003A5E04 /* ProxyServerEvent.swift in Sources */,
|
54CA029D2426B2FD003A5E04 /* ProxyServerEvent.swift in Sources */,
|
||||||
54CA02BC2426B2FD003A5E04 /* SocketProtocol.swift in Sources */,
|
54CA02BC2426B2FD003A5E04 /* SocketProtocol.swift in Sources */,
|
||||||
54CA029C2426B2FD003A5E04 /* AdapterSocketEvent.swift in Sources */,
|
54CA029C2426B2FD003A5E04 /* AdapterSocketEvent.swift in Sources */,
|
||||||
54CA02A72426B2FD003A5E04 /* DirectAdapter.swift in Sources */,
|
54CA02A72426B2FD003A5E04 /* DirectAdapter.swift in Sources */,
|
||||||
54CA02A32426B2FD003A5E04 /* HTTPAdapter.swift in Sources */,
|
|
||||||
54CA02622426B2FD003A5E04 /* GCDHTTPProxyServer.swift in Sources */,
|
54CA02622426B2FD003A5E04 /* GCDHTTPProxyServer.swift in Sources */,
|
||||||
54CA02822426B2FD003A5E04 /* AllRule.swift in Sources */,
|
|
||||||
543CDB2023EEE61900B7F323 /* PacketTunnelProvider.swift in Sources */,
|
543CDB2023EEE61900B7F323 /* PacketTunnelProvider.swift in Sources */,
|
||||||
54CA02662426B2FD003A5E04 /* NWUDPSocket.swift in Sources */,
|
54CA02662426B2FD003A5E04 /* NWUDPSocket.swift in Sources */,
|
||||||
54CA02682426B2FD003A5E04 /* NWTCPSocket.swift in Sources */,
|
54CA02682426B2FD003A5E04 /* NWTCPSocket.swift in Sources */,
|
||||||
54CA02852426B2FD003A5E04 /* DirectRule.swift in Sources */,
|
|
||||||
54CA01D32426B23D003A5E04 /* Resolver.swift in Sources */,
|
54CA01D32426B23D003A5E04 /* Resolver.swift in Sources */,
|
||||||
54CA028B2426B2FD003A5E04 /* Utils.swift in Sources */,
|
54CA028B2426B2FD003A5E04 /* Utils.swift in Sources */,
|
||||||
54CA02972426B2FD003A5E04 /* IPPacket.swift in Sources */,
|
54CA02972426B2FD003A5E04 /* IPPacket.swift in Sources */,
|
||||||
54CA026A2426B2FD003A5E04 /* RawSocketFactory.swift in Sources */,
|
54CA026A2426B2FD003A5E04 /* RawSocketFactory.swift in Sources */,
|
||||||
54CA02A02426B2FD003A5E04 /* TunnelEvent.swift in Sources */,
|
54CA02A02426B2FD003A5E04 /* TunnelEvent.swift in Sources */,
|
||||||
546063E523FEFAFE008F505A /* DBCore.swift in Sources */,
|
546063E523FEFAFE008F505A /* DBCore.swift in Sources */,
|
||||||
54CA02872426B2FD003A5E04 /* IPRangeListRule.swift in Sources */,
|
|
||||||
54CA02922426B2FD003A5E04 /* DNSSession.swift in Sources */,
|
54CA02922426B2FD003A5E04 /* DNSSession.swift in Sources */,
|
||||||
54CA026D2426B2FD003A5E04 /* Opt.swift in Sources */,
|
54CA026D2426B2FD003A5E04 /* Opt.swift in Sources */,
|
||||||
54CA02B32426B2FD003A5E04 /* HTTPAdapterFactory.swift in Sources */,
|
|
||||||
54CA02702426B2FD003A5E04 /* HTTPStreamScanner.swift in Sources */,
|
54CA02702426B2FD003A5E04 /* HTTPStreamScanner.swift in Sources */,
|
||||||
54CA02812426B2FD003A5E04 /* DNSFailRule.swift in Sources */,
|
541075DA24CE2C7200D6F1BF /* GlassVPNHook.swift in Sources */,
|
||||||
54CA02AC2426B2FD003A5E04 /* AuthenticationServerAdapterFactory.swift in Sources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -997,6 +1143,38 @@
|
|||||||
name = LaunchScreen.storyboard;
|
name = LaunchScreen.storyboard;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
543078C124B60F3B00278F2D /* Settings.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
543078C224B60F3B00278F2D /* Base */,
|
||||||
|
);
|
||||||
|
name = Settings.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
549A96D8250419B200C565FA /* CoOccurrence.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
549A96D9250419B200C565FA /* Base */,
|
||||||
|
);
|
||||||
|
name = CoOccurrence.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
54A0CC0724E30C56009B5EC1 /* Recordings.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
54A0CC0824E30C56009B5EC1 /* Base */,
|
||||||
|
);
|
||||||
|
name = Recordings.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
54A0CC0A24E30D6F009B5EC1 /* Requests.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
54A0CC0B24E30D6F009B5EC1 /* Base */,
|
||||||
|
);
|
||||||
|
name = Requests.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXVariantGroup section */
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
@@ -1026,6 +1204,7 @@
|
|||||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
@@ -1090,6 +1269,7 @@
|
|||||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
@@ -1127,7 +1307,7 @@
|
|||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = main/main.entitlements;
|
CODE_SIGN_ENTITLEMENTS = main/main.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = 22;
|
CURRENT_PROJECT_VERSION = 34;
|
||||||
INFOPLIST_FILE = main/Info.plist;
|
INFOPLIST_FILE = main/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@@ -1146,7 +1326,7 @@
|
|||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = main/main.entitlements;
|
CODE_SIGN_ENTITLEMENTS = main/main.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = 22;
|
CURRENT_PROJECT_VERSION = 34;
|
||||||
INFOPLIST_FILE = main/Info.plist;
|
INFOPLIST_FILE = main/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@@ -1165,7 +1345,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements;
|
CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 22;
|
CURRENT_PROJECT_VERSION = 34;
|
||||||
INFOPLIST_FILE = GlassVPN/Info.plist;
|
INFOPLIST_FILE = GlassVPN/Info.plist;
|
||||||
MARKETING_VERSION = 1.0.0;
|
MARKETING_VERSION = 1.0.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN";
|
PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN";
|
||||||
@@ -1183,7 +1363,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements;
|
CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 22;
|
CURRENT_PROJECT_VERSION = 34;
|
||||||
INFOPLIST_FILE = GlassVPN/Info.plist;
|
INFOPLIST_FILE = GlassVPN/Info.plist;
|
||||||
MARKETING_VERSION = 1.0.0;
|
MARKETING_VERSION = 1.0.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN";
|
PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1130"
|
LastUpgradeVersion = "1200"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1130"
|
LastUpgradeVersion = "1200"
|
||||||
wasCreatedForAppExtension = "YES"
|
wasCreatedForAppExtension = "YES"
|
||||||
version = "2.0">
|
version = "2.0">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
|
|||||||
@@ -1,50 +1,8 @@
|
|||||||
import NetworkExtension
|
import NetworkExtension
|
||||||
|
|
||||||
fileprivate var filterDomains: [String]!
|
let connectMessage: Data = "CONNECT".data(using: .ascii)!
|
||||||
fileprivate var filterOptions: [(block: Bool, ignore: Bool)]!
|
let swcdUserAgent: Data = "User-Agent: swcd".data(using: .ascii)!
|
||||||
|
fileprivate var hook : GlassVPNHook!
|
||||||
|
|
||||||
// MARK: Backward DNS Binary Tree Lookup
|
|
||||||
|
|
||||||
fileprivate func reloadDomainFilter() {
|
|
||||||
let tmp = AppDB?.loadFilters()?.map({
|
|
||||||
(String($0.reversed()), $1)
|
|
||||||
}).sorted(by: { $0.0 < $1.0 }) ?? []
|
|
||||||
filterDomains = tmp.map { $0.0 }
|
|
||||||
filterOptions = tmp.map { ($1.contains(.blocked), $1.contains(.ignored)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate func filterIndex(for domain: String) -> Int {
|
|
||||||
let reverseDomain = String(domain.reversed())
|
|
||||||
var lo = 0, hi = filterDomains.count - 1
|
|
||||||
while lo <= hi {
|
|
||||||
let mid = (lo + hi)/2
|
|
||||||
if filterDomains[mid] < reverseDomain {
|
|
||||||
lo = mid + 1
|
|
||||||
} else if reverseDomain < filterDomains[mid] {
|
|
||||||
hi = mid - 1
|
|
||||||
} else {
|
|
||||||
return mid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if lo > 0, reverseDomain.hasPrefix(filterDomains[lo - 1] + ".") {
|
|
||||||
return lo - 1
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
private let queue = DispatchQueue.init(label: "PSIGlassDNSQueue", qos: .userInteractive, target: .main)
|
|
||||||
|
|
||||||
private func logAsync(_ domain: String, blocked: Bool) {
|
|
||||||
queue.async {
|
|
||||||
do {
|
|
||||||
try AppDB?.logWrite(domain, blocked: blocked)
|
|
||||||
} catch {
|
|
||||||
DDLogWarn("Couldn't write: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: ObserverFactory
|
// MARK: ObserverFactory
|
||||||
|
|
||||||
@@ -59,14 +17,17 @@ class LDObserverFactory: ObserverFactory {
|
|||||||
override func signal(_ event: ProxySocketEvent) {
|
override func signal(_ event: ProxySocketEvent) {
|
||||||
switch event {
|
switch event {
|
||||||
case .receivedRequest(let session, let socket):
|
case .receivedRequest(let session, let socket):
|
||||||
let i = filterIndex(for: session.host)
|
var kill = !hook.isBackgroundRecording && hook.forceDisconnectUnresolvable && session.ipAddress.isEmpty
|
||||||
if i >= 0 {
|
if kill || socket.isCancelled { // isCancelled is set by branch below
|
||||||
let (block, ignore) = filterOptions[i]
|
hook.silentlyPrevented(session.host)
|
||||||
if !ignore { logAsync(session.host, blocked: block) }
|
|
||||||
if block { socket.forceDisconnect() }
|
|
||||||
} else {
|
} else {
|
||||||
// TODO: disable filter during recordings
|
kill = hook.processDNSRequest(session.host)
|
||||||
logAsync(session.host, blocked: false)
|
}
|
||||||
|
if kill { socket.forceDisconnect() }
|
||||||
|
case .readData(let data, on: let socket):
|
||||||
|
if !hook.isBackgroundRecording, hook.forceDisconnectSWCD,
|
||||||
|
data.starts(with: connectMessage), data.range(of: swcdUserAgent) != nil {
|
||||||
|
socket.disconnect() // sets isCancelled above
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
@@ -80,25 +41,63 @@ 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!
|
||||||
|
|
||||||
|
// MARK: Delegate
|
||||||
|
|
||||||
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))")
|
||||||
|
PrefsShared.registerDefaults()
|
||||||
do {
|
do {
|
||||||
try SQLiteDatabase.open().initCommonScheme()
|
try SQLiteDatabase.open().initCommonScheme()
|
||||||
} catch {
|
} catch {
|
||||||
completionHandler(error)
|
completionHandler(error) // if we cant open db, fail immediately
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
reloadDomainFilter()
|
// stop previous if any
|
||||||
|
if proxyServer != nil { proxyServer.stop() }
|
||||||
if proxyServer != nil {
|
|
||||||
proxyServer.stop()
|
|
||||||
}
|
|
||||||
proxyServer = nil
|
proxyServer = nil
|
||||||
|
|
||||||
// Create proxy
|
willInitProxy()
|
||||||
|
|
||||||
|
self.setTunnelNetworkSettings(createProxy()) { error in
|
||||||
|
guard error == nil else {
|
||||||
|
DDLogError("setTunnelNetworkSettings error: \(error!)")
|
||||||
|
completionHandler(error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.proxyServer = GCDHTTPProxyServer(address: IPAddress(fromString: self.proxyServerAddress), port: Port(port: self.proxyServerPort))
|
||||||
|
do {
|
||||||
|
try self.proxyServer.start()
|
||||||
|
self.didInitProxy()
|
||||||
|
completionHandler(nil)
|
||||||
|
} catch let proxyError {
|
||||||
|
DDLogError("Error starting proxy server \(proxyError)")
|
||||||
|
completionHandler(proxyError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||||
|
DDLogVerbose("stopTunnel with reason: \(reason)")
|
||||||
|
shutdown()
|
||||||
|
completionHandler()
|
||||||
|
exit(EXIT_SUCCESS)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
|
||||||
|
hook.handleAppMessage(messageData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Helper
|
||||||
|
|
||||||
|
private func willInitProxy() {
|
||||||
|
hook = GlassVPNHook()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createProxy() -> NEPacketTunnelNetworkSettings {
|
||||||
let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: proxyServerAddress)
|
let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: proxyServerAddress)
|
||||||
settings.mtu = NSNumber(value: 1500)
|
settings.mtu = NSNumber(value: 1500)
|
||||||
|
|
||||||
@@ -115,42 +114,29 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
settings.proxySettings = proxySettings;
|
settings.proxySettings = proxySettings;
|
||||||
RawSocketFactory.TunnelProvider = self
|
RawSocketFactory.TunnelProvider = self
|
||||||
ObserverFactory.currentFactory = LDObserverFactory()
|
ObserverFactory.currentFactory = LDObserverFactory()
|
||||||
|
return settings
|
||||||
self.setTunnelNetworkSettings(settings) { error in
|
}
|
||||||
guard error == nil else {
|
|
||||||
DDLogError("setTunnelNetworkSettings error: \(String(describing: error))")
|
private func didInitProxy() {
|
||||||
completionHandler(error)
|
if PrefsShared.RestartReminder.Enabled {
|
||||||
return
|
PushNotification.scheduleRestartReminderBadge(on: false)
|
||||||
}
|
PushNotification.cancel(.CantStopMeNowReminder)
|
||||||
completionHandler(nil)
|
|
||||||
|
|
||||||
self.proxyServer = GCDHTTPProxyServer(address: IPAddress(fromString: self.proxyServerAddress), port: Port(port: self.proxyServerPort))
|
|
||||||
do {
|
|
||||||
try self.proxyServer.start()
|
|
||||||
completionHandler(nil)
|
|
||||||
}
|
|
||||||
catch let proxyError {
|
|
||||||
DDLogError("Error starting proxy server \(proxyError)")
|
|
||||||
completionHandler(proxyError)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
private func shutdown() {
|
||||||
DDLogVerbose("stopTunnel with reason: \(reason)")
|
// proxy
|
||||||
DNSServer.currentServer = nil
|
DNSServer.currentServer = nil
|
||||||
RawSocketFactory.TunnelProvider = nil
|
RawSocketFactory.TunnelProvider = nil
|
||||||
ObserverFactory.currentFactory = nil
|
ObserverFactory.currentFactory = nil
|
||||||
proxyServer.stop()
|
proxyServer.stop()
|
||||||
proxyServer = nil
|
proxyServer = nil
|
||||||
filterDomains = nil
|
// custom
|
||||||
filterOptions = nil
|
hook.cleanUp()
|
||||||
completionHandler()
|
hook = nil
|
||||||
exit(EXIT_SUCCESS)
|
if PrefsShared.RestartReminder.Enabled {
|
||||||
}
|
PushNotification.scheduleRestartReminderBadge(on: true)
|
||||||
|
PushNotification.scheduleRestartReminderBanner()
|
||||||
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
|
}
|
||||||
reloadDomainFilter()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
GlassVPN/SwiftSocket/.DS_Store
vendored
Normal file
@@ -1,16 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
public enum RuleMatchEvent: EventType {
|
|
||||||
public var description: String {
|
|
||||||
switch self {
|
|
||||||
case let .ruleMatched(session, rule: rule):
|
|
||||||
return "Rule \(rule) matched session \(session)."
|
|
||||||
case let .ruleDidNotMatch(session, rule: rule):
|
|
||||||
return "Rule \(rule) did not match session \(session)."
|
|
||||||
case let .dnsRuleMatched(session, rule: rule, type: type, result: result):
|
|
||||||
return "Rule \(rule) matched DNS session \(session) of type \(type), the result is \(result)."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case ruleMatched(ConnectSession, rule: Rule), ruleDidNotMatch(ConnectSession, rule: Rule), dnsRuleMatched(DNSSession, rule: Rule, type: DNSSessionMatchType, result: DNSSessionMatchResult)
|
|
||||||
}
|
|
||||||
@@ -20,8 +20,4 @@ open class ObserverFactory {
|
|||||||
open func getObserverForProxyServer(_ server: ProxyServer) -> Observer<ProxyServerEvent>? {
|
open func getObserverForProxyServer(_ server: ProxyServer) -> Observer<ProxyServerEvent>? {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
open func getObserverForRuleManager(_ manager: RuleManager) -> Observer<RuleMatchEvent>? {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,8 +74,6 @@ open class DNSServer: DNSResolverDelegate, IPStackProtocol {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
RuleManager.currentManager.matchDNS(session, type: .domain)
|
|
||||||
|
|
||||||
switch session.matchResult! {
|
switch session.matchResult! {
|
||||||
case .fake:
|
case .fake:
|
||||||
guard setUpFakeIP(session) else {
|
guard setUpFakeIP(session) else {
|
||||||
@@ -248,10 +246,6 @@ open class DNSServer: DNSResolverDelegate, IPStackProtocol {
|
|||||||
|
|
||||||
session.realIP = message.resolvedIPv4Address
|
session.realIP = message.resolvedIPv4Address
|
||||||
|
|
||||||
if session.matchResult != .fake && session.matchResult != .real {
|
|
||||||
RuleManager.currentManager.matchDNS(session, type: .ip)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch session.matchResult! {
|
switch session.matchResult! {
|
||||||
case .fake:
|
case .fake:
|
||||||
if !self.setUpFakeIP(session) {
|
if !self.setUpFakeIP(session) {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ open class DNSSession {
|
|||||||
open var fakeIP: IPAddress?
|
open var fakeIP: IPAddress?
|
||||||
open var realResponseMessage: DNSMessage?
|
open var realResponseMessage: DNSMessage?
|
||||||
var realResponseIPPacket: IPPacket?
|
var realResponseIPPacket: IPPacket?
|
||||||
open var matchedRule: Rule?
|
|
||||||
open var matchResult: DNSSessionMatchResult?
|
open var matchResult: DNSSessionMatchResult?
|
||||||
var indexToMatch = 0
|
var indexToMatch = 0
|
||||||
var expireAt: Date?
|
var expireAt: Date?
|
||||||
|
|||||||
@@ -21,9 +21,6 @@ public final class ConnectSession {
|
|||||||
/// The requested port.
|
/// The requested port.
|
||||||
public let port: Int
|
public let port: Int
|
||||||
|
|
||||||
/// The rule to use to connect to remote.
|
|
||||||
public var matchedRule: Rule?
|
|
||||||
|
|
||||||
/// Whether If the `requestedHost` is an IP address.
|
/// Whether If the `requestedHost` is an IP address.
|
||||||
public let fakeIPEnabled: Bool
|
public let fakeIPEnabled: Bool
|
||||||
|
|
||||||
@@ -126,11 +123,6 @@ public final class ConnectSession {
|
|||||||
|
|
||||||
host = session.requestMessage.queries[0].name
|
host = session.requestMessage.queries[0].name
|
||||||
ipAddress = session.realIP?.presentation ?? ""
|
ipAddress = session.realIP?.presentation ?? ""
|
||||||
matchedRule = session.matchedRule
|
|
||||||
|
|
||||||
// if session.countryCode != nil {
|
|
||||||
// country = session.countryCode!
|
|
||||||
// }
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ open class HTTPHeader {
|
|||||||
// Chunk is not supported yet.
|
// Chunk is not supported yet.
|
||||||
open var contentLength: Int = 0
|
open var contentLength: Int = 0
|
||||||
open var headers: [(String, String)] = []
|
open var headers: [(String, String)] = []
|
||||||
open var rawHeader: Data?
|
|
||||||
|
|
||||||
public init(headerString: String) throws {
|
public init(headerString: String) throws {
|
||||||
let lines = headerString.components(separatedBy: "\r\n")
|
let lines = headerString.components(separatedBy: "\r\n")
|
||||||
@@ -127,7 +126,6 @@ open class HTTPHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try self.init(headerString: headerString)
|
try self.init(headerString: headerString)
|
||||||
rawHeader = headerData
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open subscript(index: String) -> String? {
|
open subscript(index: String) -> String? {
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
/// The SOCKS5 proxy server.
|
|
||||||
public final class GCDSOCKS5ProxyServer: GCDProxyServer {
|
|
||||||
/**
|
|
||||||
Create an instance of SOCKS5 proxy server.
|
|
||||||
|
|
||||||
- parameter address: The address of proxy server.
|
|
||||||
- parameter port: The port of proxy server.
|
|
||||||
*/
|
|
||||||
override public init(address: IPAddress?, port: Port) {
|
|
||||||
super.init(address: address, port: port)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Handle the new accepted socket as a SOCKS5 proxy connection.
|
|
||||||
|
|
||||||
- parameter socket: The accepted socket.
|
|
||||||
*/
|
|
||||||
override public func handleNewGCDSocket(_ socket: GCDTCPSocket) {
|
|
||||||
let proxySocket = SOCKS5ProxySocket(socket: socket)
|
|
||||||
didAcceptNewSocket(proxySocket)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -141,7 +141,14 @@ public class NWTCPSocket: NSObject, RawTCPSocketProtocol {
|
|||||||
|
|
||||||
connection!.readMinimumLength(1, maximumLength: Opt.MAXNWTCPSocketReadDataSize) { data, error in
|
connection!.readMinimumLength(1, maximumLength: Opt.MAXNWTCPSocketReadDataSize) { data, error in
|
||||||
guard error == nil else {
|
guard error == nil else {
|
||||||
DDLogError("NWTCPSocket got an error when reading data: \(String(describing: error))")
|
let e = error! as NSError
|
||||||
|
let ignore = (
|
||||||
|
e.domain == "kNWErrorDomainPOSIX" && e.code == POSIXError.ECANCELED.rawValue // Operation canceled
|
||||||
|
|| e.domain == NSPOSIXErrorDomain && e.code == POSIXError.ENOTCONN.rawValue // Socket is not connected
|
||||||
|
)
|
||||||
|
if !ignore {
|
||||||
|
DDLogError("NWTCPSocket got an error when reading data: \(String(describing: error))")
|
||||||
|
}
|
||||||
self.queueCall {
|
self.queueCall {
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
open class ResponseGeneratorFactory {
|
|
||||||
static var HTTPProxyResponseGenerator: ResponseGenerator.Type?
|
|
||||||
static var SOCKS5ProxyResponseGenerator: ResponseGenerator.Type?
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
/// The rule matches all DNS and connect sessions.
|
|
||||||
open class AllRule: Rule {
|
|
||||||
fileprivate let adapterFactory: AdapterFactory
|
|
||||||
|
|
||||||
open override var description: String {
|
|
||||||
return "<AllRule>"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Create a new `AllRule` instance.
|
|
||||||
|
|
||||||
- parameter adapterFactory: The factory which builds a corresponding adapter when needed.
|
|
||||||
*/
|
|
||||||
public init(adapterFactory: AdapterFactory) {
|
|
||||||
self.adapterFactory = adapterFactory
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Match DNS session to this rule.
|
|
||||||
|
|
||||||
- parameter session: The DNS session to match.
|
|
||||||
- parameter type: What kind of information is available.
|
|
||||||
|
|
||||||
- returns: The result of match.
|
|
||||||
*/
|
|
||||||
override open func matchDNS(_ session: DNSSession, type: DNSSessionMatchType) -> DNSSessionMatchResult {
|
|
||||||
// only return real IP when we connect to remote directly
|
|
||||||
if let _ = adapterFactory as? DirectAdapterFactory {
|
|
||||||
return .real
|
|
||||||
} else {
|
|
||||||
return .fake
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Match connect session to this rule.
|
|
||||||
|
|
||||||
- parameter session: connect session to match.
|
|
||||||
|
|
||||||
- returns: The configured adapter.
|
|
||||||
*/
|
|
||||||
override open func match(_ session: ConnectSession) -> AdapterFactory? {
|
|
||||||
return adapterFactory
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
/// The rule matches the request which failed to look up.
|
|
||||||
open class DNSFailRule: Rule {
|
|
||||||
fileprivate let adapterFactory: AdapterFactory
|
|
||||||
|
|
||||||
open override var description: String {
|
|
||||||
return "<DNSFailRule>"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Create a new `DNSFailRule` instance.
|
|
||||||
|
|
||||||
- parameter adapterFactory: The factory which builds a corresponding adapter when needed.
|
|
||||||
*/
|
|
||||||
public init(adapterFactory: AdapterFactory) {
|
|
||||||
self.adapterFactory = adapterFactory
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Match DNS request to this rule.
|
|
||||||
|
|
||||||
- parameter session: The DNS session to match.
|
|
||||||
- parameter type: What kind of information is available.
|
|
||||||
|
|
||||||
- returns: The result of match.
|
|
||||||
*/
|
|
||||||
override open func matchDNS(_ session: DNSSession, type: DNSSessionMatchType) -> DNSSessionMatchResult {
|
|
||||||
guard type == .ip else {
|
|
||||||
return .unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
// only return real IP when we connect to remote directly
|
|
||||||
if session.realIP == nil {
|
|
||||||
if let _ = adapterFactory as? DirectAdapterFactory {
|
|
||||||
return .real
|
|
||||||
} else {
|
|
||||||
return .fake
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return .pass
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Match connect session to this rule.
|
|
||||||
|
|
||||||
- parameter session: connect session to match.
|
|
||||||
|
|
||||||
- returns: The configured adapter.
|
|
||||||
*/
|
|
||||||
override open func match(_ session: ConnectSession) -> AdapterFactory? {
|
|
||||||
if session.ipAddress == "" {
|
|
||||||
return adapterFactory
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
/// The rule matches every request and returns direct adapter.
|
|
||||||
///
|
|
||||||
/// This is equivalent to create an `AllRule` with a `DirectAdapterFactory`.
|
|
||||||
open class DirectRule: AllRule {
|
|
||||||
open override var description: String {
|
|
||||||
return "<DirectRule>"
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
Create a new `DirectRule` instance.
|
|
||||||
*/
|
|
||||||
public init() {
|
|
||||||
super.init(adapterFactory: DirectAdapterFactory())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
/// The rule matches the host domain to a list of predefined criteria.
|
|
||||||
open class DomainListRule: Rule {
|
|
||||||
public enum MatchCriterion {
|
|
||||||
case regex(NSRegularExpression), prefix(String), suffix(String), keyword(String), complete(String)
|
|
||||||
|
|
||||||
func match(_ domain: String) -> Bool {
|
|
||||||
switch self {
|
|
||||||
case .regex(let regex):
|
|
||||||
return regex.firstMatch(in: domain, options: [], range: NSRange(location: 0, length: domain.utf8.count)) != nil
|
|
||||||
case .prefix(let prefix):
|
|
||||||
return domain.hasPrefix(prefix)
|
|
||||||
case .suffix(let suffix):
|
|
||||||
return domain.hasSuffix(suffix)
|
|
||||||
case .keyword(let keyword):
|
|
||||||
return domain.contains(keyword)
|
|
||||||
case .complete(let match):
|
|
||||||
return domain == match
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate let adapterFactory: AdapterFactory
|
|
||||||
|
|
||||||
open override var description: String {
|
|
||||||
return "<DomainListRule>"
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The list of criteria to match to.
|
|
||||||
open var matchCriteria: [MatchCriterion] = []
|
|
||||||
|
|
||||||
/**
|
|
||||||
Create a new `DomainListRule` instance.
|
|
||||||
|
|
||||||
- parameter adapterFactory: The factory which builds a corresponding adapter when needed.
|
|
||||||
- parameter criteria: The list of criteria to match.
|
|
||||||
*/
|
|
||||||
public init(adapterFactory: AdapterFactory, criteria: [MatchCriterion]) {
|
|
||||||
self.adapterFactory = adapterFactory
|
|
||||||
self.matchCriteria = criteria
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Match DNS request to this rule.
|
|
||||||
|
|
||||||
- parameter session: The DNS session to match.
|
|
||||||
- parameter type: What kind of information is available.
|
|
||||||
|
|
||||||
- returns: The result of match.
|
|
||||||
*/
|
|
||||||
override open func matchDNS(_ session: DNSSession, type: DNSSessionMatchType) -> DNSSessionMatchResult {
|
|
||||||
if matchDomain(session.requestMessage.queries.first!.name) {
|
|
||||||
if let _ = adapterFactory as? DirectAdapterFactory {
|
|
||||||
return .real
|
|
||||||
}
|
|
||||||
return .fake
|
|
||||||
}
|
|
||||||
return .pass
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Match connect session to this rule.
|
|
||||||
|
|
||||||
- parameter session: connect session to match.
|
|
||||||
|
|
||||||
- returns: The configured adapter if matched, return `nil` if not matched.
|
|
||||||
*/
|
|
||||||
override open func match(_ session: ConnectSession) -> AdapterFactory? {
|
|
||||||
if matchDomain(session.host) {
|
|
||||||
return adapterFactory
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate func matchDomain(_ domain: String) -> Bool {
|
|
||||||
for criterion in matchCriteria {
|
|
||||||
if criterion.match(domain) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
/// The rule matches the ip of the target hsot to a list of IP ranges.
|
|
||||||
open class IPRangeListRule: Rule {
|
|
||||||
fileprivate let adapterFactory: AdapterFactory
|
|
||||||
|
|
||||||
open override var description: String {
|
|
||||||
return "<IPRangeList>"
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The list of regular expressions to match to.
|
|
||||||
open var ranges: [IPRange] = []
|
|
||||||
|
|
||||||
/**
|
|
||||||
Create a new `IPRangeListRule` instance.
|
|
||||||
|
|
||||||
- parameter adapterFactory: The factory which builds a corresponding adapter when needed.
|
|
||||||
- parameter ranges: The list of IP ranges to match. The IP ranges are expressed in CIDR form ("127.0.0.1/8") or range form ("127.0.0.1+16777216").
|
|
||||||
|
|
||||||
- throws: The error when parsing the IP range.
|
|
||||||
*/
|
|
||||||
public init(adapterFactory: AdapterFactory, ranges: [String]) throws {
|
|
||||||
self.adapterFactory = adapterFactory
|
|
||||||
self.ranges = try ranges.map {
|
|
||||||
let range = try IPRange(withString: $0)
|
|
||||||
return range
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Match DNS request to this rule.
|
|
||||||
|
|
||||||
- parameter session: The DNS session to match.
|
|
||||||
- parameter type: What kind of information is available.
|
|
||||||
|
|
||||||
- returns: The result of match.
|
|
||||||
*/
|
|
||||||
override open func matchDNS(_ session: DNSSession, type: DNSSessionMatchType) -> DNSSessionMatchResult {
|
|
||||||
guard type == .ip else {
|
|
||||||
return .unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
// Probably we should match all answers?
|
|
||||||
guard let ip = session.realIP else {
|
|
||||||
return .pass
|
|
||||||
}
|
|
||||||
|
|
||||||
for range in ranges {
|
|
||||||
if range.contains(ip: ip) {
|
|
||||||
return .fake
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return .pass
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Match connect session to this rule.
|
|
||||||
|
|
||||||
- parameter session: connect session to match.
|
|
||||||
|
|
||||||
- returns: The configured adapter if matched, return `nil` if not matched.
|
|
||||||
*/
|
|
||||||
override open func match(_ session: ConnectSession) -> AdapterFactory? {
|
|
||||||
guard let ip = IPAddress(fromString: session.ipAddress) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for range in ranges {
|
|
||||||
if range.contains(ip: ip) {
|
|
||||||
return adapterFactory
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
/// The rule defines what to do for DNS requests and connect sessions.
|
|
||||||
open class Rule: CustomStringConvertible {
|
|
||||||
open var description: String {
|
|
||||||
return "<Rule>"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Create a new rule.
|
|
||||||
*/
|
|
||||||
public init() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Match DNS request to this rule.
|
|
||||||
|
|
||||||
- parameter session: The DNS session to match.
|
|
||||||
- parameter type: What kind of information is available.
|
|
||||||
|
|
||||||
- returns: The result of match.
|
|
||||||
*/
|
|
||||||
open func matchDNS(_ session: DNSSession, type: DNSSessionMatchType) -> DNSSessionMatchResult {
|
|
||||||
return .real
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Match connect session to this rule.
|
|
||||||
|
|
||||||
- parameter session: connect session to match.
|
|
||||||
|
|
||||||
- returns: The configured adapter if matched, return `nil` if not matched.
|
|
||||||
*/
|
|
||||||
open func match(_ session: ConnectSession) -> AdapterFactory? {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
/// The class managing rules.
|
|
||||||
open class RuleManager {
|
|
||||||
/// The current used `RuleManager`, there is only one manager should be used at a time.
|
|
||||||
///
|
|
||||||
/// - note: This should be set before any DNS or connect sessions.
|
|
||||||
public static var currentManager: RuleManager = RuleManager(fromRules: [], appendDirect: true)
|
|
||||||
|
|
||||||
/// The rule list.
|
|
||||||
var rules: [Rule] = []
|
|
||||||
|
|
||||||
open var observer: Observer<RuleMatchEvent>?
|
|
||||||
|
|
||||||
/**
|
|
||||||
Create a new `RuleManager` from the given rules.
|
|
||||||
|
|
||||||
- parameter rules: The rules.
|
|
||||||
- parameter appendDirect: Whether to append a `DirectRule` at the end of the list so any request does not match with any rule go directly.
|
|
||||||
*/
|
|
||||||
public init(fromRules rules: [Rule], appendDirect: Bool = false) {
|
|
||||||
self.rules = []
|
|
||||||
|
|
||||||
if appendDirect || self.rules.count == 0 {
|
|
||||||
self.rules.append(DirectRule())
|
|
||||||
}
|
|
||||||
|
|
||||||
observer = ObserverFactory.currentFactory?.getObserverForRuleManager(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Match DNS request to all rules.
|
|
||||||
|
|
||||||
- parameter session: The DNS session to match.
|
|
||||||
- parameter type: What kind of information is available.
|
|
||||||
*/
|
|
||||||
func matchDNS(_ session: DNSSession, type: DNSSessionMatchType) {
|
|
||||||
for (i, rule) in rules[session.indexToMatch..<rules.count].enumerated() {
|
|
||||||
let result = rule.matchDNS(session, type: type)
|
|
||||||
|
|
||||||
observer?.signal(.dnsRuleMatched(session, rule: rule, type: type, result: result))
|
|
||||||
|
|
||||||
switch result {
|
|
||||||
case .fake, .real, .unknown:
|
|
||||||
session.matchedRule = rule
|
|
||||||
session.matchResult = result
|
|
||||||
session.indexToMatch = i + session.indexToMatch // add the offset
|
|
||||||
return
|
|
||||||
case .pass:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Match connect session to all rules.
|
|
||||||
|
|
||||||
- parameter session: connect session to match.
|
|
||||||
|
|
||||||
- returns: The matched configured adapter.
|
|
||||||
*/
|
|
||||||
func match(_ session: ConnectSession) -> AdapterFactory! {
|
|
||||||
if session.matchedRule != nil {
|
|
||||||
observer?.signal(.ruleMatched(session, rule: session.matchedRule!))
|
|
||||||
return session.matchedRule!.match(session)
|
|
||||||
}
|
|
||||||
|
|
||||||
for rule in rules {
|
|
||||||
if let adapterFactory = rule.match(session) {
|
|
||||||
observer?.signal(.ruleMatched(session, rule: rule))
|
|
||||||
|
|
||||||
session.matchedRule = rule
|
|
||||||
return adapterFactory
|
|
||||||
} else {
|
|
||||||
observer?.signal(.ruleDidNotMatch(session, rule: rule))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil // this should never happens
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
/// This is a very simple wrapper of a dict of type `[String: AdapterFactory]`.
|
|
||||||
///
|
|
||||||
/// Use it as a normal dict.
|
|
||||||
public class AdapterFactoryManager {
|
|
||||||
private var factoryDict: [String: AdapterFactory]
|
|
||||||
|
|
||||||
public subscript(index: String) -> AdapterFactory? {
|
|
||||||
get {
|
|
||||||
if index == "direct" {
|
|
||||||
return DirectAdapterFactory()
|
|
||||||
}
|
|
||||||
return factoryDict[index]
|
|
||||||
}
|
|
||||||
set { factoryDict[index] = newValue }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Initialize a new factory manager.
|
|
||||||
|
|
||||||
- parameter factoryDict: The factory dict.
|
|
||||||
*/
|
|
||||||
public init(factoryDict: [String: AdapterFactory]) {
|
|
||||||
self.factoryDict = factoryDict
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
/// Factory building server adapter which requires authentication.
|
|
||||||
open class HTTPAuthenticationAdapterFactory: ServerAdapterFactory {
|
|
||||||
let auth: HTTPAuthentication?
|
|
||||||
|
|
||||||
required public init(serverHost: String, serverPort: Int, auth: HTTPAuthentication?) {
|
|
||||||
self.auth = auth
|
|
||||||
super.init(serverHost: serverHost, serverPort: serverPort)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
/// Factory building HTTP adapter.
|
|
||||||
open class HTTPAdapterFactory: HTTPAuthenticationAdapterFactory {
|
|
||||||
required public init(serverHost: String, serverPort: Int, auth: HTTPAuthentication?) {
|
|
||||||
super.init(serverHost: serverHost, serverPort: serverPort, auth: auth)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Get a HTTP adapter.
|
|
||||||
|
|
||||||
- parameter session: The connect session.
|
|
||||||
|
|
||||||
- returns: The built adapter.
|
|
||||||
*/
|
|
||||||
override open func getAdapterFor(session: ConnectSession) -> AdapterSocket {
|
|
||||||
let adapter = HTTPAdapter(serverHost: serverHost, serverPort: serverPort, auth: auth)
|
|
||||||
adapter.socket = RawSocketFactory.getRawSocket()
|
|
||||||
return adapter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
open class RejectAdapterFactory: AdapterFactory {
|
|
||||||
public let delay: Int
|
|
||||||
|
|
||||||
public init(delay: Int = Opt.RejectAdapterDefaultDelay) {
|
|
||||||
self.delay = delay
|
|
||||||
}
|
|
||||||
|
|
||||||
override open func getAdapterFor(session: ConnectSession) -> AdapterSocket {
|
|
||||||
return RejectAdapter(delay: delay)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
/// Factory building SOCKS5 adapter.
|
|
||||||
open class SOCKS5AdapterFactory: ServerAdapterFactory {
|
|
||||||
override public init(serverHost: String, serverPort: Int) {
|
|
||||||
super.init(serverHost: serverHost, serverPort: serverPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Get a SOCKS5 adapter.
|
|
||||||
|
|
||||||
- parameter session: The connect session.
|
|
||||||
|
|
||||||
- returns: The built adapter.
|
|
||||||
*/
|
|
||||||
override open func getAdapterFor(session: ConnectSession) -> AdapterSocket {
|
|
||||||
let adapter = SOCKS5Adapter(serverHost: serverHost, serverPort: serverPort)
|
|
||||||
adapter.socket = RawSocketFactory.getRawSocket()
|
|
||||||
return adapter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
/// Factory building secured HTTP (HTTP with SSL) adapter.
|
|
||||||
open class SecureHTTPAdapterFactory: HTTPAdapterFactory {
|
|
||||||
required public init(serverHost: String, serverPort: Int, auth: HTTPAuthentication?) {
|
|
||||||
super.init(serverHost: serverHost, serverPort: serverPort, auth: auth)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Get a secured HTTP adapter.
|
|
||||||
|
|
||||||
- parameter session: The connect session.
|
|
||||||
|
|
||||||
- returns: The built adapter.
|
|
||||||
*/
|
|
||||||
override open func getAdapterFor(session: ConnectSession) -> AdapterSocket {
|
|
||||||
let adapter = SecureHTTPAdapter(serverHost: serverHost, serverPort: serverPort, auth: auth)
|
|
||||||
adapter.socket = RawSocketFactory.getRawSocket()
|
|
||||||
return adapter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
/// Factory building adapter with proxy server host and port.
|
|
||||||
open class ServerAdapterFactory: AdapterFactory {
|
|
||||||
let serverHost: String
|
|
||||||
let serverPort: Int
|
|
||||||
|
|
||||||
public init(serverHost: String, serverPort: Int) {
|
|
||||||
self.serverHost = serverHost
|
|
||||||
self.serverPort = serverPort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
public enum HTTPAdapterError: Error, CustomStringConvertible {
|
|
||||||
case invalidURL, serailizationFailure
|
|
||||||
|
|
||||||
public var description: String {
|
|
||||||
switch self {
|
|
||||||
case .invalidURL:
|
|
||||||
return "Invalid url when connecting through proxy"
|
|
||||||
case .serailizationFailure:
|
|
||||||
return "Failed to serialize HTTP CONNECT header"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This adapter connects to remote host through a HTTP proxy.
|
|
||||||
public class HTTPAdapter: AdapterSocket {
|
|
||||||
enum HTTPAdapterStatus {
|
|
||||||
case invalid,
|
|
||||||
connecting,
|
|
||||||
readingResponse,
|
|
||||||
forwarding,
|
|
||||||
stopped
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The host domain of the HTTP proxy.
|
|
||||||
let serverHost: String
|
|
||||||
|
|
||||||
/// The port of the HTTP proxy.
|
|
||||||
let serverPort: Int
|
|
||||||
|
|
||||||
/// The authentication information for the HTTP proxy.
|
|
||||||
let auth: HTTPAuthentication?
|
|
||||||
|
|
||||||
/// Whether the connection to the proxy should be secured or not.
|
|
||||||
var secured: Bool
|
|
||||||
|
|
||||||
var internalStatus: HTTPAdapterStatus = .invalid
|
|
||||||
|
|
||||||
public init(serverHost: String, serverPort: Int, auth: HTTPAuthentication?) {
|
|
||||||
self.serverHost = serverHost
|
|
||||||
self.serverPort = serverPort
|
|
||||||
self.auth = auth
|
|
||||||
secured = false
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func openSocketWith(session: ConnectSession) {
|
|
||||||
super.openSocketWith(session: session)
|
|
||||||
|
|
||||||
guard !isCancelled else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
internalStatus = .connecting
|
|
||||||
try socket.connectTo(host: serverHost, port: serverPort, enableTLS: secured, tlsSettings: nil)
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func didConnectWith(socket: RawTCPSocketProtocol) {
|
|
||||||
super.didConnectWith(socket: socket)
|
|
||||||
|
|
||||||
guard let url = URL(string: "\(session.host):\(session.port)") else {
|
|
||||||
observer?.signal(.errorOccured(HTTPAdapterError.invalidURL, on: self))
|
|
||||||
disconnect()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let message = CFHTTPMessageCreateRequest(kCFAllocatorDefault, "CONNECT" as CFString, url as CFURL, kCFHTTPVersion1_1).takeRetainedValue()
|
|
||||||
if let authData = auth {
|
|
||||||
CFHTTPMessageSetHeaderFieldValue(message, "Proxy-Authorization" as CFString, authData.authString() as CFString?)
|
|
||||||
}
|
|
||||||
CFHTTPMessageSetHeaderFieldValue(message, "Host" as CFString, "\(session.host):\(session.port)" as CFString?)
|
|
||||||
CFHTTPMessageSetHeaderFieldValue(message, "Content-Length" as CFString, "0" as CFString?)
|
|
||||||
|
|
||||||
guard let requestData = CFHTTPMessageCopySerializedMessage(message)?.takeRetainedValue() else {
|
|
||||||
observer?.signal(.errorOccured(HTTPAdapterError.serailizationFailure, on: self))
|
|
||||||
disconnect()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
internalStatus = .readingResponse
|
|
||||||
write(data: requestData as Data)
|
|
||||||
socket.readDataTo(data: Utils.HTTPData.DoubleCRLF)
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func didRead(data: Data, from socket: RawTCPSocketProtocol) {
|
|
||||||
super.didRead(data: data, from: socket)
|
|
||||||
|
|
||||||
switch internalStatus {
|
|
||||||
case .readingResponse:
|
|
||||||
internalStatus = .forwarding
|
|
||||||
observer?.signal(.readyForForward(self))
|
|
||||||
delegate?.didBecomeReadyToForwardWith(socket: self)
|
|
||||||
case .forwarding:
|
|
||||||
observer?.signal(.readData(data, on: self))
|
|
||||||
delegate?.didRead(data: data, from: self)
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func didWrite(data: Data?, by socket: RawTCPSocketProtocol) {
|
|
||||||
super.didWrite(data: data, by: socket)
|
|
||||||
if internalStatus == .forwarding {
|
|
||||||
observer?.signal(.wroteData(data, on: self))
|
|
||||||
delegate?.didWrite(data: data, by: self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
public class RejectAdapter: AdapterSocket {
|
|
||||||
public let delay: Int
|
|
||||||
|
|
||||||
public init(delay: Int) {
|
|
||||||
self.delay = delay
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func openSocketWith(session: ConnectSession) {
|
|
||||||
super.openSocketWith(session: session)
|
|
||||||
|
|
||||||
QueueFactory.getQueue().asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.milliseconds(delay)) {
|
|
||||||
[weak self] in
|
|
||||||
self?.disconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Disconnect the socket elegantly.
|
|
||||||
*/
|
|
||||||
public override func disconnect(becauseOf error: Error? = nil) {
|
|
||||||
guard !isCancelled else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_cancelled = true
|
|
||||||
session.disconnected(becauseOf: error, by: .adapter)
|
|
||||||
observer?.signal(.disconnectCalled(self))
|
|
||||||
_status = .closed
|
|
||||||
delegate?.didDisconnectWith(socket: self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Disconnect the socket immediately.
|
|
||||||
*/
|
|
||||||
public override func forceDisconnect(becauseOf error: Error? = nil) {
|
|
||||||
guard !isCancelled else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_cancelled = true
|
|
||||||
session.disconnected(becauseOf: error, by: .adapter)
|
|
||||||
observer?.signal(.forceDisconnectCalled(self))
|
|
||||||
_status = .closed
|
|
||||||
delegate?.didDisconnectWith(socket: self)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
public class SOCKS5Adapter: AdapterSocket {
|
|
||||||
enum SOCKS5AdapterStatus {
|
|
||||||
case invalid,
|
|
||||||
connecting,
|
|
||||||
readingMethodResponse,
|
|
||||||
readingResponseFirstPart,
|
|
||||||
readingResponseSecondPart,
|
|
||||||
forwarding
|
|
||||||
}
|
|
||||||
public let serverHost: String
|
|
||||||
public let serverPort: Int
|
|
||||||
|
|
||||||
var internalStatus: SOCKS5AdapterStatus = .invalid
|
|
||||||
|
|
||||||
let helloData = Data([0x05, 0x01, 0x00])
|
|
||||||
|
|
||||||
public enum ReadTag: Int {
|
|
||||||
case methodResponse = -20000, connectResponseFirstPart, connectResponseSecondPart
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum WriteTag: Int {
|
|
||||||
case open = -21000, connectIPv4, connectIPv6, connectDomainLength, connectPort
|
|
||||||
}
|
|
||||||
|
|
||||||
public init(serverHost: String, serverPort: Int) {
|
|
||||||
self.serverHost = serverHost
|
|
||||||
self.serverPort = serverPort
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
public override func openSocketWith(session: ConnectSession) {
|
|
||||||
super.openSocketWith(session: session)
|
|
||||||
|
|
||||||
guard !isCancelled else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
internalStatus = .connecting
|
|
||||||
try socket.connectTo(host: serverHost, port: serverPort, enableTLS: false, tlsSettings: nil)
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override func didConnectWith(socket: RawTCPSocketProtocol) {
|
|
||||||
super.didConnectWith(socket: socket)
|
|
||||||
|
|
||||||
write(data: helloData)
|
|
||||||
internalStatus = .readingMethodResponse
|
|
||||||
socket.readDataTo(length: 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
public override func didRead(data: Data, from socket: RawTCPSocketProtocol) {
|
|
||||||
super.didRead(data: data, from: socket)
|
|
||||||
|
|
||||||
switch internalStatus {
|
|
||||||
case .readingMethodResponse:
|
|
||||||
var response: [UInt8]
|
|
||||||
if session.isIPv4() {
|
|
||||||
response = [0x05, 0x01, 0x00, 0x01]
|
|
||||||
let address = IPAddress(fromString: session.host)!
|
|
||||||
response += [UInt8](address.dataInNetworkOrder)
|
|
||||||
} else if session.isIPv6() {
|
|
||||||
response = [0x05, 0x01, 0x00, 0x04]
|
|
||||||
let address = IPAddress(fromString: session.host)!
|
|
||||||
response += [UInt8](address.dataInNetworkOrder)
|
|
||||||
} else {
|
|
||||||
response = [0x05, 0x01, 0x00, 0x03]
|
|
||||||
response.append(UInt8(session.host.utf8.count))
|
|
||||||
response += [UInt8](session.host.utf8)
|
|
||||||
}
|
|
||||||
|
|
||||||
let portBytes: [UInt8] = Utils.toByteArray(UInt16(session.port)).reversed()
|
|
||||||
response.append(contentsOf: portBytes)
|
|
||||||
write(data: Data(response))
|
|
||||||
|
|
||||||
internalStatus = .readingResponseFirstPart
|
|
||||||
socket.readDataTo(length: 5)
|
|
||||||
case .readingResponseFirstPart:
|
|
||||||
var readLength = 0
|
|
||||||
switch data[3] {
|
|
||||||
case 1:
|
|
||||||
readLength = 3 + 2
|
|
||||||
case 3:
|
|
||||||
readLength = Int(data[4]) + 2
|
|
||||||
case 4:
|
|
||||||
readLength = 15 + 2
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
internalStatus = .readingResponseSecondPart
|
|
||||||
socket.readDataTo(length: readLength)
|
|
||||||
case .readingResponseSecondPart:
|
|
||||||
internalStatus = .forwarding
|
|
||||||
observer?.signal(.readyForForward(self))
|
|
||||||
delegate?.didBecomeReadyToForwardWith(socket: self)
|
|
||||||
case .forwarding:
|
|
||||||
delegate?.didRead(data: data, from: self)
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override open func didWrite(data: Data?, by socket: RawTCPSocketProtocol) {
|
|
||||||
super.didWrite(data: data, by: socket)
|
|
||||||
|
|
||||||
if internalStatus == .forwarding {
|
|
||||||
delegate?.didWrite(data: data, by: self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
/// This adapter connects to remote host through a HTTP proxy with SSL.
|
|
||||||
public class SecureHTTPAdapter: HTTPAdapter {
|
|
||||||
override public init(serverHost: String, serverPort: Int, auth: HTTPAuthentication?) {
|
|
||||||
super.init(serverHost: serverHost, serverPort: serverPort, auth: auth)
|
|
||||||
secured = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
/// This class just forwards data directly.
|
|
||||||
/// - note: It is designed to work with tun2socks only.
|
|
||||||
public class DirectProxySocket: ProxySocket {
|
|
||||||
enum DirectProxyReadStatus: CustomStringConvertible {
|
|
||||||
case invalid,
|
|
||||||
forwarding,
|
|
||||||
stopped
|
|
||||||
|
|
||||||
var description: String {
|
|
||||||
switch self {
|
|
||||||
case .invalid:
|
|
||||||
return "invalid"
|
|
||||||
case .forwarding:
|
|
||||||
return "forwarding"
|
|
||||||
case .stopped:
|
|
||||||
return "stopped"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum DirectProxyWriteStatus {
|
|
||||||
case invalid,
|
|
||||||
forwarding,
|
|
||||||
stopped
|
|
||||||
|
|
||||||
var description: String {
|
|
||||||
switch self {
|
|
||||||
case .invalid:
|
|
||||||
return "invalid"
|
|
||||||
case .forwarding:
|
|
||||||
return "forwarding"
|
|
||||||
case .stopped:
|
|
||||||
return "stopped"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var readStatus: DirectProxyReadStatus = .invalid
|
|
||||||
private var writeStatus: DirectProxyWriteStatus = .invalid
|
|
||||||
|
|
||||||
public var readStatusDescription: String {
|
|
||||||
return readStatus.description
|
|
||||||
}
|
|
||||||
|
|
||||||
public var writeStatusDescription: String {
|
|
||||||
return writeStatus.description
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Begin reading and processing data from the socket.
|
|
||||||
|
|
||||||
- note: Since there is nothing to read and process before forwarding data, this just calls `delegate?.didReceiveRequest`.
|
|
||||||
*/
|
|
||||||
override public func openSocket() {
|
|
||||||
super.openSocket()
|
|
||||||
|
|
||||||
guard !isCancelled else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if let address = socket.destinationIPAddress, let port = socket.destinationPort {
|
|
||||||
session = ConnectSession(host: address.presentation, port: Int(port.value))
|
|
||||||
|
|
||||||
observer?.signal(.receivedRequest(session!, on: self))
|
|
||||||
delegate?.didReceive(session: session!, from: self)
|
|
||||||
} else {
|
|
||||||
forceDisconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Response to the `AdapterSocket` on the other side of the `Tunnel` which has succefully connected to the remote server.
|
|
||||||
|
|
||||||
- parameter adapter: The `AdapterSocket`.
|
|
||||||
*/
|
|
||||||
override public func respondTo(adapter: AdapterSocket) {
|
|
||||||
super.respondTo(adapter: adapter)
|
|
||||||
|
|
||||||
guard !isCancelled else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
readStatus = .forwarding
|
|
||||||
writeStatus = .forwarding
|
|
||||||
|
|
||||||
observer?.signal(.readyForForward(self))
|
|
||||||
delegate?.didBecomeReadyToForwardWith(socket: self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
The socket did read some data.
|
|
||||||
|
|
||||||
- parameter data: The data read from the socket.
|
|
||||||
- parameter from: The socket where the data is read from.
|
|
||||||
*/
|
|
||||||
override open func didRead(data: Data, from: RawTCPSocketProtocol) {
|
|
||||||
super.didRead(data: data, from: from)
|
|
||||||
delegate?.didRead(data: data, from: self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
The socket did send some data.
|
|
||||||
|
|
||||||
- parameter data: The data which have been sent to remote (acknowledged). Note this may not be available since the data may be released to save memory.
|
|
||||||
- parameter by: The socket where the data is sent out.
|
|
||||||
*/
|
|
||||||
override open func didWrite(data: Data?, by: RawTCPSocketProtocol) {
|
|
||||||
super.didWrite(data: data, by: by)
|
|
||||||
delegate?.didWrite(data: data, by: self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,244 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
public class SOCKS5ProxySocket: ProxySocket {
|
|
||||||
enum SOCKS5ProxyReadStatus: CustomStringConvertible {
|
|
||||||
case invalid,
|
|
||||||
readingVersionIdentifierAndNumberOfMethods,
|
|
||||||
readingMethods,
|
|
||||||
readingConnectHeader,
|
|
||||||
readingIPv4Address,
|
|
||||||
readingDomainLength,
|
|
||||||
readingDomain,
|
|
||||||
readingIPv6Address,
|
|
||||||
readingPort,
|
|
||||||
forwarding,
|
|
||||||
stopped
|
|
||||||
|
|
||||||
var description: String {
|
|
||||||
switch self {
|
|
||||||
case .invalid:
|
|
||||||
return "invalid"
|
|
||||||
case .readingVersionIdentifierAndNumberOfMethods:
|
|
||||||
return "reading version and methods"
|
|
||||||
case .readingMethods:
|
|
||||||
return "reading methods"
|
|
||||||
case .readingConnectHeader:
|
|
||||||
return "reading connect header"
|
|
||||||
case .readingIPv4Address:
|
|
||||||
return "IPv4 address"
|
|
||||||
case .readingDomainLength:
|
|
||||||
return "domain length"
|
|
||||||
case .readingDomain:
|
|
||||||
return "domain"
|
|
||||||
case .readingIPv6Address:
|
|
||||||
return "IPv6 address"
|
|
||||||
case .readingPort:
|
|
||||||
return "reading port"
|
|
||||||
case .forwarding:
|
|
||||||
return "forwarding"
|
|
||||||
case .stopped:
|
|
||||||
return "stopped"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum SOCKS5ProxyWriteStatus: CustomStringConvertible {
|
|
||||||
case invalid,
|
|
||||||
sendingResponse,
|
|
||||||
forwarding,
|
|
||||||
stopped
|
|
||||||
|
|
||||||
var description: String {
|
|
||||||
switch self {
|
|
||||||
case .invalid:
|
|
||||||
return "invalid"
|
|
||||||
case .sendingResponse:
|
|
||||||
return "sending response"
|
|
||||||
case .forwarding:
|
|
||||||
return "forwarding"
|
|
||||||
case .stopped:
|
|
||||||
return "stopped"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// The remote host to connect to.
|
|
||||||
public var destinationHost: String!
|
|
||||||
|
|
||||||
/// The remote port to connect to.
|
|
||||||
public var destinationPort: Int!
|
|
||||||
|
|
||||||
private var readStatus: SOCKS5ProxyReadStatus = .invalid
|
|
||||||
private var writeStatus: SOCKS5ProxyWriteStatus = .invalid
|
|
||||||
|
|
||||||
public var readStatusDescription: String {
|
|
||||||
return readStatus.description
|
|
||||||
}
|
|
||||||
|
|
||||||
public var writeStatusDescription: String {
|
|
||||||
return writeStatus.description
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Begin reading and processing data from the socket.
|
|
||||||
*/
|
|
||||||
override public func openSocket() {
|
|
||||||
super.openSocket()
|
|
||||||
|
|
||||||
guard !isCancelled else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
readStatus = .readingVersionIdentifierAndNumberOfMethods
|
|
||||||
socket.readDataTo(length: 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// swiftlint:disable function_body_length
|
|
||||||
// swiftlint:disable cyclomatic_complexity
|
|
||||||
/**
|
|
||||||
The socket did read some data.
|
|
||||||
|
|
||||||
- parameter data: The data read from the socket.
|
|
||||||
- parameter from: The socket where the data is read from.
|
|
||||||
*/
|
|
||||||
override public func didRead(data: Data, from: RawTCPSocketProtocol) {
|
|
||||||
super.didRead(data: data, from: from)
|
|
||||||
|
|
||||||
switch readStatus {
|
|
||||||
case .forwarding:
|
|
||||||
delegate?.didRead(data: data, from: self)
|
|
||||||
case .readingVersionIdentifierAndNumberOfMethods:
|
|
||||||
data.withUnsafeBytes { pointer in
|
|
||||||
let p = pointer.bindMemory(to: Int8.self)
|
|
||||||
|
|
||||||
guard p.baseAddress!.pointee == 5 else {
|
|
||||||
// TODO: notify observer
|
|
||||||
self.disconnect()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard p.baseAddress!.successor().pointee > 0 else {
|
|
||||||
// TODO: notify observer
|
|
||||||
self.disconnect()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.readStatus = .readingMethods
|
|
||||||
self.socket.readDataTo(length: Int(p.baseAddress!.successor().pointee))
|
|
||||||
}
|
|
||||||
case .readingMethods:
|
|
||||||
// TODO: check for 0x00 in read data
|
|
||||||
|
|
||||||
let response = Data([0x05, 0x00])
|
|
||||||
// we would not be able to read anything before the data is written out, so no need to handle the dataWrote event.
|
|
||||||
write(data: response)
|
|
||||||
readStatus = .readingConnectHeader
|
|
||||||
socket.readDataTo(length: 4)
|
|
||||||
case .readingConnectHeader:
|
|
||||||
data.withUnsafeBytes { pointer in
|
|
||||||
let p = pointer.bindMemory(to: Int8.self)
|
|
||||||
|
|
||||||
guard p.baseAddress!.pointee == 5 && p.baseAddress!.successor().pointee == 1 else {
|
|
||||||
// TODO: notify observer
|
|
||||||
self.disconnect()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch p.baseAddress!.advanced(by: 3).pointee {
|
|
||||||
case 1:
|
|
||||||
readStatus = .readingIPv4Address
|
|
||||||
socket.readDataTo(length: 4)
|
|
||||||
case 3:
|
|
||||||
readStatus = .readingDomainLength
|
|
||||||
socket.readDataTo(length: 1)
|
|
||||||
case 4:
|
|
||||||
readStatus = .readingIPv6Address
|
|
||||||
socket.readDataTo(length: 16)
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case .readingIPv4Address:
|
|
||||||
var address = Data(count: Int(INET_ADDRSTRLEN))
|
|
||||||
_ = data.withUnsafeBytes { data_ptr in
|
|
||||||
address.withUnsafeMutableBytes { addr_ptr in
|
|
||||||
inet_ntop(AF_INET, data_ptr.baseAddress!, addr_ptr.bindMemory(to: Int8.self).baseAddress!, socklen_t(INET_ADDRSTRLEN))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
destinationHost = String(data: address, encoding: .utf8)
|
|
||||||
|
|
||||||
readStatus = .readingPort
|
|
||||||
socket.readDataTo(length: 2)
|
|
||||||
case .readingIPv6Address:
|
|
||||||
var address = Data(count: Int(INET6_ADDRSTRLEN))
|
|
||||||
_ = data.withUnsafeBytes { data_ptr in
|
|
||||||
address.withUnsafeMutableBytes { addr_ptr in
|
|
||||||
inet_ntop(AF_INET6, data_ptr.baseAddress!, addr_ptr.bindMemory(to: Int8.self).baseAddress!, socklen_t(INET6_ADDRSTRLEN))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
destinationHost = String(data: address, encoding: .utf8)
|
|
||||||
|
|
||||||
readStatus = .readingPort
|
|
||||||
socket.readDataTo(length: 2)
|
|
||||||
case .readingDomainLength:
|
|
||||||
readStatus = .readingDomain
|
|
||||||
socket.readDataTo(length: Int(data.first!))
|
|
||||||
case .readingDomain:
|
|
||||||
destinationHost = String(data: data, encoding: .utf8)
|
|
||||||
readStatus = .readingPort
|
|
||||||
socket.readDataTo(length: 2)
|
|
||||||
case .readingPort:
|
|
||||||
data.withUnsafeBytes {
|
|
||||||
destinationPort = Int($0.load(as: UInt16.self).bigEndian)
|
|
||||||
}
|
|
||||||
|
|
||||||
readStatus = .forwarding
|
|
||||||
session = ConnectSession(host: destinationHost, port: destinationPort)
|
|
||||||
observer?.signal(.receivedRequest(session!, on: self))
|
|
||||||
delegate?.didReceive(session: session!, from: self)
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
The socket did send some data.
|
|
||||||
|
|
||||||
- parameter data: The data which have been sent to remote (acknowledged). Note this may not be available since the data may be released to save memory.
|
|
||||||
- parameter from: The socket where the data is sent out.
|
|
||||||
*/
|
|
||||||
override public func didWrite(data: Data?, by: RawTCPSocketProtocol) {
|
|
||||||
super.didWrite(data: data, by: by)
|
|
||||||
|
|
||||||
switch writeStatus {
|
|
||||||
case .forwarding:
|
|
||||||
delegate?.didWrite(data: data, by: self)
|
|
||||||
case .sendingResponse:
|
|
||||||
writeStatus = .forwarding
|
|
||||||
observer?.signal(.readyForForward(self))
|
|
||||||
delegate?.didBecomeReadyToForwardWith(socket: self)
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Response to the `AdapterSocket` on the other side of the `Tunnel` which has succefully connected to the remote server.
|
|
||||||
|
|
||||||
- parameter adapter: The `AdapterSocket`.
|
|
||||||
*/
|
|
||||||
override public func respondTo(adapter: AdapterSocket) {
|
|
||||||
super.respondTo(adapter: adapter)
|
|
||||||
|
|
||||||
guard !isCancelled else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var responseBytes = [UInt8](repeating: 0, count: 10)
|
|
||||||
responseBytes[0...3] = [0x05, 0x00, 0x00, 0x01]
|
|
||||||
let responseData = Data(responseBytes)
|
|
||||||
|
|
||||||
writeStatus = .sendingResponse
|
|
||||||
write(data: responseData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -170,9 +170,7 @@ public class Tunnel: NSObject, SocketDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let manager = RuleManager.currentManager
|
adapterSocket = DirectAdapterFactory().getAdapterFor(session: session)
|
||||||
let factory = manager.match(session)!
|
|
||||||
adapterSocket = factory.getAdapterFor(session: session)
|
|
||||||
adapterSocket!.delegate = self
|
adapterSocket!.delegate = self
|
||||||
adapterSocket!.openSocketWith(session: session)
|
adapterSocket!.openSocketWith(session: session)
|
||||||
}
|
}
|
||||||
|
|||||||
22
README.md
@@ -16,6 +16,8 @@ Your data belongs to you.
|
|||||||
Therefore, monitoring and analysis take place on your device only.
|
Therefore, monitoring and analysis take place on your device only.
|
||||||
The app does not share any data with us or any other third-party – unless you choose to.
|
The app does not share any data with us or any other third-party – unless you choose to.
|
||||||
|
|
||||||
|
Join [Testflight beta](https://testflight.apple.com/join/9jjaFeHO)
|
||||||
|
|
||||||
|
|
||||||
### How does it work?
|
### How does it work?
|
||||||
|
|
||||||
@@ -31,19 +33,25 @@ That means, AppCheck does not have to be active in the foreground all the time.
|
|||||||
- See history of previous connections
|
- See history of previous connections
|
||||||
- Block unwanted traffic based on domain names
|
- Block unwanted traffic based on domain names
|
||||||
- Record app specific activity<sup>1</sup>
|
- Record app specific activity<sup>1</sup>
|
||||||
- Apply logging filters
|
- Apply logging filters (block or ignore) and display filters (specific range or last x minutes)
|
||||||
|
- Sort results by time, name, or occurrence count
|
||||||
**… and soon:**
|
- Context Analysis
|
||||||
|
- What other domains occur often at the same time?
|
||||||
|
- What happened immediately before or after the action?
|
||||||
|
- Export results for custom analysis
|
||||||
- Alert Monitor & reminder
|
- Alert Monitor & reminder
|
||||||
- Occurrence Context Analysis
|
|
||||||
- Participate in privacy research
|
- Participate in privacy research
|
||||||
|
- Contribute your results
|
||||||
|
- See what others have unveiled
|
||||||
|
- How much traffic does this app produce?
|
||||||
|
|
||||||
|
|
||||||
<sup>1</sup> Due to technical limitations, recording is not limited to any single application. Remember to force-quit all other applications before starting a recording.
|
<sup>1</sup> Due to technical limitations, recordings can not be restricted to a single application. Remember to force-quit all other applications before starting a recording.
|
||||||
|
|
||||||
|
|
||||||
## Research Project
|
## Research Project
|
||||||
|
|
||||||
*information will be added soon*
|
*information will be added soon™*
|
||||||
|
|
||||||
|
For now, go to the results page at [https://appchk.de/](https://appchk.de/).
|
||||||
|
Btw. we are searching for [help](https://appchk.de/help/) on our ongoing research project.
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 158 KiB |
@@ -1,13 +1,9 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import NetworkExtension
|
|
||||||
|
|
||||||
let VPNConfigBundleIdentifier = "de.uni-bamberg.psi.AppCheck.VPN"
|
|
||||||
|
|
||||||
@UIApplicationMain
|
@UIApplicationMain
|
||||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
var window: UIWindow?
|
var window: UIWindow?
|
||||||
var managerVPN: NETunnelProviderManager?
|
|
||||||
|
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
if UserDefaults.standard.bool(forKey: "kill_db") {
|
if UserDefaults.standard.bool(forKey: "kill_db") {
|
||||||
@@ -19,123 +15,25 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
db.initAppOnlyScheme()
|
db.initAppOnlyScheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
#if IOS_SIMULATOR
|
Prefs.registerDefaults()
|
||||||
TestDataSource.load()
|
PrefsShared.registerDefaults()
|
||||||
#endif
|
|
||||||
|
|
||||||
loadVPN { mgr in
|
#if IOS_SIMULATOR
|
||||||
self.managerVPN = mgr
|
SimulatorVPN.load()
|
||||||
self.postVPNState()
|
#endif
|
||||||
}
|
|
||||||
NSNotification.Name.NEVPNStatusDidChange.observe(call: #selector(vpnStatusChanged(_:)), on: self)
|
|
||||||
NotifyDNSFilterChanged.observe(call: #selector(didChangeDomainFilter), on: self)
|
|
||||||
|
|
||||||
sync.start()
|
sync.start()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func vpnStatusChanged(_ notification: Notification) {
|
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||||
postRawVPNState((notification.object as? NETunnelProviderSession)?.status ?? .invalid)
|
TheGreatDestroyer.deleteLogs(olderThan: PrefsShared.AutoDeleteLogsDays)
|
||||||
}
|
// FIXME: Does not reflect changes performed by GlassVPN auto-delete while app is open.
|
||||||
|
// It will update whenever app restarts or becomes active again (only if deleteLogs has something to delete!)
|
||||||
@objc private func didChangeDomainFilter() {
|
// This is a known issue and tolerated.
|
||||||
// Notify VPN extension about changes
|
|
||||||
if let session = self.managerVPN?.connection as? NETunnelProviderSession,
|
|
||||||
session.status == .connected {
|
|
||||||
try? session.sendProviderMessage("filter-update".data(using: .ascii)!, responseHandler: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setProxyEnabled(_ newState: Bool) {
|
|
||||||
guard let mgr = self.managerVPN else {
|
|
||||||
self.createNewVPN { manager in
|
|
||||||
self.managerVPN = manager
|
|
||||||
self.setProxyEnabled(newState)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let state = mgr.isEnabled && (mgr.connection.status == .connected)
|
|
||||||
if state != newState {
|
|
||||||
self.updateVPN({ mgr.isEnabled = true }) {
|
|
||||||
newState ? try? mgr.connection.startVPNTunnel() : mgr.connection.stopVPNTunnel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: VPN
|
|
||||||
|
|
||||||
private func createNewVPN(_ success: @escaping (_ manager: NETunnelProviderManager) -> Void) {
|
|
||||||
let mgr = NETunnelProviderManager()
|
|
||||||
mgr.localizedDescription = "AppCheck Monitor"
|
|
||||||
let proto = NETunnelProviderProtocol()
|
|
||||||
proto.providerBundleIdentifier = VPNConfigBundleIdentifier
|
|
||||||
proto.serverAddress = "127.0.0.1"
|
|
||||||
mgr.protocolConfiguration = proto
|
|
||||||
mgr.isEnabled = true
|
|
||||||
mgr.saveToPreferences { error in
|
|
||||||
guard error == nil else {
|
|
||||||
self.postProcessedVPNState(.off)
|
|
||||||
//ErrorAlert(error!).presentIn(self.window?.rootViewController)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
success(mgr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func loadVPN(_ finally: @escaping (_ manager: NETunnelProviderManager?) -> Void) {
|
|
||||||
NETunnelProviderManager.loadAllFromPreferences { managers, error in
|
|
||||||
guard let mgrs = managers, mgrs.count > 0 else {
|
|
||||||
finally(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for mgr in mgrs {
|
|
||||||
if let proto = (mgr.protocolConfiguration as? NETunnelProviderProtocol) {
|
|
||||||
if proto.providerBundleIdentifier == VPNConfigBundleIdentifier {
|
|
||||||
finally(mgr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateVPN(_ body: @escaping () -> Void, _ onSuccess: @escaping () -> Void) {
|
|
||||||
self.managerVPN?.loadFromPreferences { error in
|
|
||||||
guard error == nil else { return }
|
|
||||||
body()
|
|
||||||
self.managerVPN?.saveToPreferences { error in
|
|
||||||
guard error == nil else { return }
|
|
||||||
onSuccess()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func postVPNState() {
|
|
||||||
guard let mgr = self.managerVPN else {
|
|
||||||
self.postRawVPNState(.invalid)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mgr.loadFromPreferences { _ in
|
|
||||||
self.postRawVPNState(mgr.connection.status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Notifications
|
|
||||||
|
|
||||||
private func postRawVPNState(_ origState: NEVPNStatus) {
|
|
||||||
let state: VPNState
|
|
||||||
switch origState {
|
|
||||||
case .connected: state = .on
|
|
||||||
case .connecting, .disconnecting, .reasserting: state = .inbetween
|
|
||||||
case .invalid, .disconnected: fallthrough
|
|
||||||
@unknown default: state = .off
|
|
||||||
}
|
|
||||||
postProcessedVPNState(state)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func postProcessedVPNState(_ state: VPNState) {
|
|
||||||
currentVPNState = state
|
|
||||||
NotifyVPNStateChanged.post(state)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension URL {
|
||||||
|
@discardableResult func open() -> Bool { UIApplication.shared.openURL(self) }
|
||||||
|
}
|
||||||
|
|||||||
BIN
main/Assets.xcassets/.DS_Store
vendored
26
main/Assets.xcassets/circle-check.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "img.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "img@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "img@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
main/Assets.xcassets/circle-check.imageset/img.png
vendored
Normal file
|
After Width: | Height: | Size: 247 B |
BIN
main/Assets.xcassets/circle-check.imageset/img@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 423 B |
BIN
main/Assets.xcassets/circle-check.imageset/img@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 572 B |
26
main/Assets.xcassets/circle-x.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "img.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "img@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "img@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
main/Assets.xcassets/circle-x.imageset/img.png
vendored
Normal file
|
After Width: | Height: | Size: 235 B |
BIN
main/Assets.xcassets/circle-x.imageset/img@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 400 B |
BIN
main/Assets.xcassets/circle-x.imageset/img@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 530 B |
26
main/Assets.xcassets/detail-help.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "img.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "img@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "img@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
main/Assets.xcassets/detail-help.imageset/img.png
vendored
Normal file
|
After Width: | Height: | Size: 322 B |
BIN
main/Assets.xcassets/detail-help.imageset/img@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 510 B |
BIN
main/Assets.xcassets/detail-help.imageset/img@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 701 B |
@@ -19,5 +19,8 @@
|
|||||||
"info" : {
|
"info" : {
|
||||||
"author" : "xcode",
|
"author" : "xcode",
|
||||||
"version" : 1
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,5 +19,8 @@
|
|||||||
"info" : {
|
"info" : {
|
||||||
"author" : "xcode",
|
"author" : "xcode",
|
||||||
"version" : 1
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,5 +19,8 @@
|
|||||||
"info" : {
|
"info" : {
|
||||||
"author" : "xcode",
|
"author" : "xcode",
|
||||||
"version" : 1
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
26
main/Assets.xcassets/jump-to-target.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "img.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "img@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "img@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
main/Assets.xcassets/jump-to-target.imageset/img.png
vendored
Normal file
|
After Width: | Height: | Size: 230 B |
BIN
main/Assets.xcassets/jump-to-target.imageset/img@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 409 B |
BIN
main/Assets.xcassets/jump-to-target.imageset/img@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 544 B |
26
main/Assets.xcassets/line-collapse.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "img.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "img@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "img@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
main/Assets.xcassets/line-collapse.imageset/img.png
vendored
Normal file
|
After Width: | Height: | Size: 150 B |
BIN
main/Assets.xcassets/line-collapse.imageset/img@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 216 B |
BIN
main/Assets.xcassets/line-collapse.imageset/img@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 283 B |
26
main/Assets.xcassets/line-expand.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "img.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "img@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "img@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
main/Assets.xcassets/line-expand.imageset/img.png
vendored
Normal file
|
After Width: | Height: | Size: 156 B |
BIN
main/Assets.xcassets/line-expand.imageset/img@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 224 B |
BIN
main/Assets.xcassets/line-expand.imageset/img@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 282 B |
237
main/Common Classes/CustomAlert.swift
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
import UIKit
|
||||||
|
|
||||||
|
class CustomAlert<CustomView: UIView>: UIViewController {
|
||||||
|
|
||||||
|
private let alertTitle: String?
|
||||||
|
private let alertDetail: String?
|
||||||
|
|
||||||
|
private let customView: CustomView
|
||||||
|
private var callback: ((CustomView) -> Void)?
|
||||||
|
|
||||||
|
/// Default: `[Cancel, Save]`
|
||||||
|
lazy var buttonsBar: UIStackView = {
|
||||||
|
let cancel = QuickUI.button("Cancel", target: self, action: #selector(didTapCancel))
|
||||||
|
let save = QuickUI.button("Save", target: self, action: #selector(didTapSave))
|
||||||
|
save.titleLabel?.font = save.titleLabel?.font.bold()
|
||||||
|
let bar = UIStackView(arrangedSubviews: [cancel, save])
|
||||||
|
bar.axis = .horizontal
|
||||||
|
bar.distribution = .equalSpacing
|
||||||
|
return bar
|
||||||
|
}()
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||||
|
|
||||||
|
init(title: String? = nil, detail: String? = nil, view custom: CustomView) {
|
||||||
|
alertTitle = title
|
||||||
|
alertDetail = detail
|
||||||
|
customView = custom
|
||||||
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
override var isModalInPresentation: Bool { set{} get{true} }
|
||||||
|
override var modalPresentationStyle: UIModalPresentationStyle { set{} get{.custom} }
|
||||||
|
override var transitioningDelegate: UIViewControllerTransitioningDelegate? {
|
||||||
|
set {} get {
|
||||||
|
SlideInTransitioningDelegate(for: .bottom, modal: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override func loadView() {
|
||||||
|
let control = UIView()
|
||||||
|
control.backgroundColor = .sysBackground
|
||||||
|
view = control
|
||||||
|
|
||||||
|
var tmpPrevivous: UIView? = nil
|
||||||
|
|
||||||
|
func adaptive(margin: CGFloat, _ fn: () -> NSLayoutConstraint) {
|
||||||
|
regularConstraints.append(fn() + margin)
|
||||||
|
compactConstraints.append(fn() + margin/2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addLabel(_ lbl: UILabel) {
|
||||||
|
lbl.numberOfLines = 0
|
||||||
|
control.addSubview(lbl)
|
||||||
|
lbl.anchor([.leading, .trailing], to: control.layoutMarginsGuide)
|
||||||
|
if let p = tmpPrevivous {
|
||||||
|
adaptive(margin: 16) { lbl.topAnchor =&= p.bottomAnchor }
|
||||||
|
} else {
|
||||||
|
adaptive(margin: 12) { lbl.topAnchor =&= control.layoutMarginsGuide.topAnchor }
|
||||||
|
}
|
||||||
|
tmpPrevivous = lbl
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alert title & description
|
||||||
|
if let t = alertTitle {
|
||||||
|
let lbl = QuickUI.label(t, align: .center, style: .subheadline)
|
||||||
|
lbl.font = lbl.font.bold()
|
||||||
|
addLabel(lbl)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let d = alertDetail {
|
||||||
|
addLabel(QuickUI.label(d, align: .center, style: .footnote))
|
||||||
|
}
|
||||||
|
|
||||||
|
// User content
|
||||||
|
control.addSubview(customView)
|
||||||
|
customView.anchor([.leading, .trailing], to: control)
|
||||||
|
if let p = tmpPrevivous {
|
||||||
|
customView.topAnchor =&= p.bottomAnchor | .defaultHigh
|
||||||
|
} else {
|
||||||
|
customView.topAnchor =&= control.layoutMarginsGuide.topAnchor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action buttons
|
||||||
|
control.addSubview(buttonsBar)
|
||||||
|
buttonsBar.anchor([.leading, .trailing], to: control.layoutMarginsGuide, margin: 8)
|
||||||
|
buttonsBar.topAnchor =&= customView.bottomAnchor | .defaultHigh
|
||||||
|
|
||||||
|
adaptive(margin: 12) { control.layoutMarginsGuide.bottomAnchor =&= buttonsBar.bottomAnchor }
|
||||||
|
|
||||||
|
adaptToNewTraits(traitCollection)
|
||||||
|
view.frame.size = view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Adaptive Traits
|
||||||
|
|
||||||
|
private var compactConstraints: [NSLayoutConstraint] = []
|
||||||
|
private var regularConstraints: [NSLayoutConstraint] = []
|
||||||
|
|
||||||
|
private func adaptToNewTraits(_ traits: UITraitCollection) {
|
||||||
|
let flag = traits.verticalSizeClass == .compact
|
||||||
|
NSLayoutConstraint.deactivate(flag ? regularConstraints : compactConstraints)
|
||||||
|
NSLayoutConstraint.activate(flag ? compactConstraints : regularConstraints)
|
||||||
|
view.setNeedsLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||||
|
super.willTransition(to: newCollection, with: coordinator)
|
||||||
|
adaptToNewTraits(newCollection)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - User Interaction
|
||||||
|
|
||||||
|
override var keyCommands: [UIKeyCommand]? {
|
||||||
|
[UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(didTapCancel))]
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func didTapCancel() {
|
||||||
|
callback = nil
|
||||||
|
dismiss(animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func didTapSave() {
|
||||||
|
dismiss(animated: true) {
|
||||||
|
self.callback?(self.customView)
|
||||||
|
self.callback = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Present & Dismiss
|
||||||
|
|
||||||
|
func present(in viewController: UIViewController, onSuccess: @escaping (CustomView) -> Void) {
|
||||||
|
callback = onSuccess
|
||||||
|
viewController.present(self, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ###################################
|
||||||
|
// #
|
||||||
|
// # MARK: - Date Picker Alert
|
||||||
|
// #
|
||||||
|
// ###################################
|
||||||
|
|
||||||
|
class DatePickerAlert : CustomAlert<UIDatePicker> {
|
||||||
|
|
||||||
|
let datePicker = UIDatePicker()
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||||
|
|
||||||
|
init(title: String? = nil, detail: String? = nil, initial date: Date? = nil) {
|
||||||
|
if let date = date {
|
||||||
|
datePicker.setDate(date, animated: false)
|
||||||
|
}
|
||||||
|
super.init(title: title, detail: detail, view: datePicker)
|
||||||
|
|
||||||
|
let now = QuickUI.button("Now", target: self, action: #selector(didTapNow))
|
||||||
|
now.titleLabel?.font = now.titleLabel?.font.bold()
|
||||||
|
now.setTitleColor(.sysLabel, for: .normal)
|
||||||
|
buttonsBar.insertArrangedSubview(now, at: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func didTapNow() {
|
||||||
|
datePicker.date = Date()
|
||||||
|
}
|
||||||
|
|
||||||
|
func present(in viewController: UIViewController, onSuccess: @escaping (UIDatePicker, Date) -> Void) {
|
||||||
|
super.present(in: viewController) {
|
||||||
|
onSuccess($0, $0.date)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #######################################
|
||||||
|
// #
|
||||||
|
// # MARK: - Duration Picker Alert
|
||||||
|
// #
|
||||||
|
// #######################################
|
||||||
|
|
||||||
|
class DurationPickerAlert: CustomAlert<UIPickerView>, UIPickerViewDataSource, UIPickerViewDelegate {
|
||||||
|
|
||||||
|
let pickerView = UIPickerView()
|
||||||
|
private let dataSource: [[String]]
|
||||||
|
private let compWidths: [CGFloat]
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||||
|
|
||||||
|
/// - Parameter options: [[List of labels] per component]
|
||||||
|
/// - Parameter widths: If `nil` set all components to equal width
|
||||||
|
init(title: String? = nil, detail: String? = nil, options: [[String]], widths: [CGFloat]? = nil) {
|
||||||
|
assert(widths == nil || widths!.count == options.count, "widths.count != options.count")
|
||||||
|
|
||||||
|
dataSource = options
|
||||||
|
compWidths = widths ?? options.map { _ in 1 / CGFloat(options.count) }
|
||||||
|
|
||||||
|
super.init(title: title, detail: detail, view: pickerView)
|
||||||
|
|
||||||
|
pickerView.dataSource = self
|
||||||
|
pickerView.delegate = self
|
||||||
|
}
|
||||||
|
|
||||||
|
func numberOfComponents(in _: UIPickerView) -> Int {
|
||||||
|
dataSource.count
|
||||||
|
}
|
||||||
|
func pickerView(_: UIPickerView, numberOfRowsInComponent c: Int) -> Int {
|
||||||
|
dataSource[c].count
|
||||||
|
}
|
||||||
|
func pickerView(_: UIPickerView, titleForRow r: Int, forComponent c: Int) -> String? {
|
||||||
|
dataSource[c][r]
|
||||||
|
}
|
||||||
|
func pickerView(_ pickerView: UIPickerView, widthForComponent c: Int) -> CGFloat {
|
||||||
|
compWidths[c] * pickerView.frame.width
|
||||||
|
}
|
||||||
|
|
||||||
|
func present(in viewController: UIViewController, onSuccess: @escaping (UIPickerView, [Int]) -> Void) {
|
||||||
|
super.present(in: viewController) {
|
||||||
|
onSuccess($0, $0.selection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UIPickerView {
|
||||||
|
var selection: [Int] {
|
||||||
|
get { (0..<numberOfComponents).map { selectedRow(inComponent: $0) } }
|
||||||
|
set { setSelection(newValue) }
|
||||||
|
}
|
||||||
|
/// - Warning: Does not check for boundaries!
|
||||||
|
func setSelection(_ selection: [Int], animated: Bool = false) {
|
||||||
|
assert(selection.count == numberOfComponents, "selection.count != components.count")
|
||||||
|
for (c, i) in selection.enumerated() {
|
||||||
|
selectRow(i, inComponent: c, animated: animated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
import UIKit
|
|
||||||
|
|
||||||
class DatePickerAlert: UIViewController {
|
|
||||||
|
|
||||||
override var keyCommands: [UIKeyCommand]? {
|
|
||||||
[UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(didTapCancel))]
|
|
||||||
}
|
|
||||||
|
|
||||||
private var callback: (Date) -> Void
|
|
||||||
private let picker: UIDatePicker = {
|
|
||||||
let x = UIDatePicker()
|
|
||||||
let h = x.sizeThatFits(.zero).height
|
|
||||||
x.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: h)
|
|
||||||
return x
|
|
||||||
}()
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
|
||||||
|
|
||||||
@discardableResult required init(presentIn viewController: UIViewController, configure: ((UIDatePicker) -> Void)? = nil, onSuccess: @escaping (Date) -> Void) {
|
|
||||||
callback = onSuccess
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
|
||||||
modalPresentationStyle = .custom
|
|
||||||
if #available(iOS 13.0, *) {
|
|
||||||
isModalInPresentation = true
|
|
||||||
}
|
|
||||||
presentIn(viewController, configure)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override func loadView() {
|
|
||||||
let cancel = QuickUI.button("Discard", target: self, action: #selector(didTapCancel))
|
|
||||||
let save = QuickUI.button("Save", target: self, action: #selector(didTapSave))
|
|
||||||
let now = QuickUI.button("Now", target: self, action: #selector(didTapNow))
|
|
||||||
save.titleLabel?.font = save.titleLabel?.font.bold()
|
|
||||||
now.titleLabel?.font = now.titleLabel?.font.bold()
|
|
||||||
now.setTitleColor(.sysFg, for: .normal)
|
|
||||||
//cancel.setTitleColor(.systemRed, for: .normal)
|
|
||||||
|
|
||||||
let buttons = UIStackView(arrangedSubviews: [cancel, now, save])
|
|
||||||
buttons.axis = .horizontal
|
|
||||||
buttons.distribution = .equalSpacing
|
|
||||||
|
|
||||||
let bg = UIView(frame: picker.frame)
|
|
||||||
bg.frame.size.height += buttons.frame.height + 15
|
|
||||||
bg.frame.origin.y = UIScreen.main.bounds.height - bg.frame.height - 15
|
|
||||||
bg.backgroundColor = .sysBg
|
|
||||||
bg.addSubview(picker)
|
|
||||||
bg.addSubview(buttons)
|
|
||||||
|
|
||||||
let clearBg = UIView()
|
|
||||||
clearBg.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
||||||
clearBg.addSubview(bg)
|
|
||||||
|
|
||||||
picker.anchor([.leading, .trailing, .top], to: bg)
|
|
||||||
picker.bottomAnchor =&= buttons.topAnchor
|
|
||||||
buttons.anchor([.leading, .trailing], to: bg, margin: 25)
|
|
||||||
buttons.bottomAnchor =&= bg.bottomAnchor - 15
|
|
||||||
bg.anchor([.leading, .trailing, .bottom], to: clearBg)
|
|
||||||
|
|
||||||
view = clearBg
|
|
||||||
view.isHidden = true // otherwise picker will flash on present
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func didTapNow() {
|
|
||||||
picker.date = Date()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func didTapSave() {
|
|
||||||
dismiss(animated: true) {
|
|
||||||
self.callback(self.picker.date)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func didTapCancel() {
|
|
||||||
dismiss(animated: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func presentIn(_ viewController: UIViewController, _ configure: ((UIDatePicker) -> Void)? = nil) {
|
|
||||||
viewController.present(self, animated: false) {
|
|
||||||
let control = self.view.subviews.first!
|
|
||||||
let prev = control.frame.origin.y
|
|
||||||
control.frame.origin.y += control.frame.height
|
|
||||||
self.view.isHidden = false
|
|
||||||
|
|
||||||
configure?(self.picker)
|
|
||||||
|
|
||||||
UIView.animate(withDuration: 0.3) {
|
|
||||||
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
|
|
||||||
control.frame.origin.y = prev
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
|
||||||
UIView.animate(withDuration: 0.3, animations: {
|
|
||||||
let control = self.view.subviews.first!
|
|
||||||
self.view.backgroundColor = .clear
|
|
||||||
control.frame.origin.y += control.frame.height
|
|
||||||
}) { _ in
|
|
||||||
super.dismiss(animated: false, completion: completion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -66,7 +66,7 @@ class TagLabel: UILabel {
|
|||||||
@IBDesignable
|
@IBDesignable
|
||||||
class MeterBar: UIView {
|
class MeterBar: UIView {
|
||||||
@IBInspectable var percent: CGFloat = 0 { didSet { setNeedsDisplay() } }
|
@IBInspectable var percent: CGFloat = 0 { didSet { setNeedsDisplay() } }
|
||||||
@IBInspectable var barColor: UIColor = .sysFg
|
@IBInspectable var barColor: UIColor = .sysLink
|
||||||
@IBInspectable var horizontal: Bool = false
|
@IBInspectable var horizontal: Bool = false
|
||||||
|
|
||||||
private var normPercent: CGFloat { 1 - max(0, min(percent, 1)) }
|
private var normPercent: CGFloat { 1 - max(0, min(percent, 1)) }
|
||||||
|
|||||||
71
main/Common Classes/NotificationBanner.swift
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import UIKit
|
||||||
|
|
||||||
|
struct NotificationBanner {
|
||||||
|
enum Style {
|
||||||
|
case fail, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
let view: UIView
|
||||||
|
|
||||||
|
init(_ msg: String, style: Style) {
|
||||||
|
let bg, fg: UIColor
|
||||||
|
let imgName: String
|
||||||
|
switch style {
|
||||||
|
case .fail:
|
||||||
|
bg = .systemRed
|
||||||
|
fg = UIColor.black.withAlphaComponent(0.80)
|
||||||
|
imgName = "circle-x"
|
||||||
|
case .ok:
|
||||||
|
bg = .systemGreen
|
||||||
|
fg = UIColor.black.withAlphaComponent(0.65)
|
||||||
|
imgName = "circle-check"
|
||||||
|
}
|
||||||
|
view = UIView()
|
||||||
|
view.backgroundColor = bg
|
||||||
|
let lbl = QuickUI.label(msg, style: .callout)
|
||||||
|
lbl.textColor = fg
|
||||||
|
lbl.numberOfLines = 0
|
||||||
|
lbl.font = lbl.font.bold()
|
||||||
|
let img = QuickUI.image(UIImage(named: imgName))
|
||||||
|
img.tintColor = fg
|
||||||
|
view.addSubview(lbl)
|
||||||
|
view.addSubview(img)
|
||||||
|
img.anchor([.centerY], to: lbl)
|
||||||
|
lbl.anchor([.bottom, .trailing], to: view.layoutMarginsGuide)
|
||||||
|
img.widthAnchor =&= 25
|
||||||
|
img.heightAnchor =&= 25
|
||||||
|
if #available(iOS 11, *) {
|
||||||
|
img.leadingAnchor =&= view.layoutMarginsGuide.leadingAnchor
|
||||||
|
lbl.topAnchor =&= view.layoutMarginsGuide.topAnchor
|
||||||
|
} else {
|
||||||
|
img.leadingAnchor =&= view.leadingAnchor + 8
|
||||||
|
lbl.topAnchor =&= view.topAnchor + 8
|
||||||
|
}
|
||||||
|
lbl.leadingAnchor =&= img.trailingAnchor + 8
|
||||||
|
img.bottomAnchor =<= view.bottomAnchor - 8 | .init(rawValue: 999)
|
||||||
|
lbl.bottomAnchor =<= view.bottomAnchor - 8 | .init(rawValue: 999)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Animate header banner from the top of the view. Show for `delay` seconds and then hide again.
|
||||||
|
/// - Parameter onClose: Run after the close animation finishes.
|
||||||
|
func present(in vc: UIViewController, hideAfter delay: TimeInterval = 3, onClose: (() -> Void)? = nil) {
|
||||||
|
vc.view.addSubview(view)
|
||||||
|
view.anchor([.leading, .trailing], to: vc.view!)
|
||||||
|
view.widthAnchor =&= vc.view!.widthAnchor // Bug? left-right is not sufficient
|
||||||
|
vc.view.layoutIfNeeded() // sets the height
|
||||||
|
let h = view.frame.height
|
||||||
|
let constraint = view.topAnchor =&= vc.view.topAnchor - h
|
||||||
|
vc.view.layoutIfNeeded() // hide view
|
||||||
|
UIView.animate(withDuration: 0.3, animations: {
|
||||||
|
constraint.constant = 0
|
||||||
|
vc.view.layoutIfNeeded() // animate view
|
||||||
|
UIView.animate(withDuration: 0.3, delay: delay, options: .curveLinear, animations: {
|
||||||
|
constraint.constant = -h
|
||||||
|
vc.view.layoutIfNeeded() // hide again
|
||||||
|
}, completion: { _ in
|
||||||
|
self.view.removeFromSuperview()
|
||||||
|
onClose?()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
126
main/Common Classes/Prefs.swift
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum Prefs {
|
||||||
|
private static var suite: UserDefaults { UserDefaults.standard }
|
||||||
|
|
||||||
|
private static func Int(_ key: String) -> Int { suite.integer(forKey: key) }
|
||||||
|
private static func Int(_ key: String, _ val: Int) { suite.set(val, forKey: key) }
|
||||||
|
private static func Bool(_ key: String) -> Bool { suite.bool(forKey: key) }
|
||||||
|
private static func Bool(_ key: String, _ val: Bool) { suite.set(val, forKey: key) }
|
||||||
|
private static func Str(_ key: String) -> String? { suite.string(forKey: key) }
|
||||||
|
private static func Str(_ key: String, _ val: String?) { suite.set(val, forKey: key) }
|
||||||
|
private static func Obj(_ key: String) -> Any? { suite.object(forKey: key) }
|
||||||
|
private static func Obj(_ key: String, _ val: Any?) { suite.set(val, forKey: key) }
|
||||||
|
|
||||||
|
static func registerDefaults() {
|
||||||
|
suite.register(defaults: [
|
||||||
|
"RecordingReminderEnabled" : true,
|
||||||
|
"contextAnalyisCoOccurrenceTime" : 5,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Tutorial
|
||||||
|
|
||||||
|
extension Prefs {
|
||||||
|
enum DidShowTutorial {
|
||||||
|
static var Welcome: Bool {
|
||||||
|
get { Prefs.Bool("didShowTutorialAppWelcome") }
|
||||||
|
set { Prefs.Bool("didShowTutorialAppWelcome", newValue) }
|
||||||
|
}
|
||||||
|
static var Recordings: Bool {
|
||||||
|
get { Prefs.Bool("didShowTutorialRecordings") }
|
||||||
|
set { Prefs.Bool("didShowTutorialRecordings", newValue) }
|
||||||
|
}
|
||||||
|
static var RecordingHowTo: Bool {
|
||||||
|
get { Prefs.Bool("didShowTutorialRecordingHowTo") }
|
||||||
|
set { Prefs.Bool("didShowTutorialRecordingHowTo", newValue) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Date Filter
|
||||||
|
|
||||||
|
enum DateFilterKind: Int {
|
||||||
|
case Off = 0, LastXMin = 1, ABRange = 2;
|
||||||
|
}
|
||||||
|
enum DateFilterOrderBy: Int {
|
||||||
|
case Date = 0, Name = 1, Count = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Prefs {
|
||||||
|
enum DateFilter {
|
||||||
|
static var Kind: DateFilterKind {
|
||||||
|
get { DateFilterKind(rawValue: Prefs.Int("dateFilterType"))! }
|
||||||
|
set { Prefs.Int("dateFilterType", newValue.rawValue) }
|
||||||
|
}
|
||||||
|
/// Default: `0` (disabled)
|
||||||
|
static var LastXMin: Int {
|
||||||
|
get { Prefs.Int("dateFilterLastXMin") }
|
||||||
|
set { Prefs.Int("dateFilterLastXMin", newValue) }
|
||||||
|
}
|
||||||
|
/// Default: `nil` (disabled)
|
||||||
|
static var RangeA: Timestamp? {
|
||||||
|
get { Prefs.Obj("dateFilterRangeA") as? Timestamp }
|
||||||
|
set { Prefs.Obj("dateFilterRangeA", newValue) }
|
||||||
|
}
|
||||||
|
/// Default: `nil` (disabled)
|
||||||
|
static var RangeB: Timestamp? {
|
||||||
|
get { Prefs.Obj("dateFilterRangeB") as? Timestamp }
|
||||||
|
set { Prefs.Obj("dateFilterRangeB", newValue) }
|
||||||
|
}
|
||||||
|
/// default: `.Date`
|
||||||
|
static var OrderBy: DateFilterOrderBy {
|
||||||
|
get { DateFilterOrderBy(rawValue: Prefs.Int("dateFilterOderType"))! }
|
||||||
|
set { Prefs.Int("dateFilterOderType", newValue.rawValue) }
|
||||||
|
}
|
||||||
|
/// default: `false` (Desc)
|
||||||
|
static var OrderAsc: Bool {
|
||||||
|
get { Prefs.Bool("dateFilterOderAsc") }
|
||||||
|
set { Prefs.Bool("dateFilterOderAsc", newValue) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// - Returns: Timestamp restriction depending on current selected date filter.
|
||||||
|
/// - `Off` : `(nil, nil)`
|
||||||
|
/// - `LastXMin` : `(now-LastXMin, nil)`
|
||||||
|
/// - `ABRange` : `(RangeA, RangeB)`
|
||||||
|
static func restrictions() -> (type: DateFilterKind, earliest: Timestamp?, latest: Timestamp?) {
|
||||||
|
let type = Kind
|
||||||
|
switch type {
|
||||||
|
case .Off: return (type, nil, nil)
|
||||||
|
case .LastXMin: return (type, Timestamp.past(minutes: Prefs.DateFilter.LastXMin), nil)
|
||||||
|
case .ABRange: return (type, Prefs.DateFilter.RangeA, Prefs.DateFilter.RangeB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - ContextAnalyis
|
||||||
|
|
||||||
|
extension Prefs {
|
||||||
|
enum ContextAnalyis {
|
||||||
|
static var CoOccurrenceTime: Int {
|
||||||
|
get { Prefs.Int("contextAnalyisCoOccurrenceTime") }
|
||||||
|
set { Prefs.Int("contextAnalyisCoOccurrenceTime", newValue) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Notifications
|
||||||
|
|
||||||
|
extension Prefs {
|
||||||
|
enum RecordingReminder {
|
||||||
|
static var Enabled: Bool {
|
||||||
|
get { Prefs.Bool("RecordingReminderEnabled") }
|
||||||
|
set { Prefs.Bool("RecordingReminderEnabled", newValue) }
|
||||||
|
}
|
||||||
|
static var Sound: String {
|
||||||
|
get { Prefs.Str("RecordingReminderSound") ?? "#default" }
|
||||||
|
set { Prefs.Str("RecordingReminderSound", newValue) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
105
main/Common Classes/PrefsShared.swift
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum PrefsShared {
|
||||||
|
private static var suite: UserDefaults { UserDefaults(suiteName: "group.de.uni-bamberg.psi.AppCheck")! }
|
||||||
|
|
||||||
|
private static func Int(_ key: String) -> Int { suite.integer(forKey: key) }
|
||||||
|
private static func Int(_ key: String, _ val: Int) { suite.set(val, forKey: key); suite.synchronize() }
|
||||||
|
private static func Bool(_ key: String) -> Bool { suite.bool(forKey: key) }
|
||||||
|
private static func Bool(_ key: String, _ val: Bool) { suite.set(val, forKey: key); suite.synchronize() }
|
||||||
|
private static func Str(_ key: String) -> String? { suite.string(forKey: key) }
|
||||||
|
private static func Str(_ key: String, _ val: String?) { suite.set(val, forKey: key); suite.synchronize() }
|
||||||
|
|
||||||
|
static func registerDefaults() {
|
||||||
|
suite.register(defaults: [
|
||||||
|
"ForceDisconnectSWCD" : true,
|
||||||
|
"RestartReminderEnabled" : true,
|
||||||
|
"RestartReminderWithText" : true,
|
||||||
|
"RestartReminderWithBadge" : true,
|
||||||
|
"ConnectionAlertsListsElse" : true,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
static var AutoDeleteLogsDays: Int {
|
||||||
|
get { Int("AutoDeleteLogsDays") }
|
||||||
|
set { Int("AutoDeleteLogsDays", newValue) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Recording State
|
||||||
|
|
||||||
|
enum CurrentRecordingState : Int {
|
||||||
|
case Off = 0, App = 1, Background = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PrefsShared {
|
||||||
|
static var CurrentlyRecording: CurrentRecordingState {
|
||||||
|
get { CurrentRecordingState(rawValue: Int("CurrentlyRecording")) ?? .Off }
|
||||||
|
set { Int("CurrentlyRecording", newValue.rawValue) }
|
||||||
|
}
|
||||||
|
static var ForceDisconnectUnresolvableDNS: Bool {
|
||||||
|
get { PrefsShared.Bool("ForceDisconnectUnresolvableDNS") }
|
||||||
|
set { PrefsShared.Bool("ForceDisconnectUnresolvableDNS", newValue) }
|
||||||
|
}
|
||||||
|
static var ForceDisconnectSWCD: Bool {
|
||||||
|
get { PrefsShared.Bool("ForceDisconnectSWCD") }
|
||||||
|
set { PrefsShared.Bool("ForceDisconnectSWCD", newValue) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Notifications
|
||||||
|
|
||||||
|
extension PrefsShared {
|
||||||
|
enum RestartReminder {
|
||||||
|
static var Enabled: Bool {
|
||||||
|
get { PrefsShared.Bool("RestartReminderEnabled") }
|
||||||
|
set { PrefsShared.Bool("RestartReminderEnabled", newValue) }
|
||||||
|
}
|
||||||
|
static var WithText: Bool {
|
||||||
|
get { PrefsShared.Bool("RestartReminderWithText") }
|
||||||
|
set { PrefsShared.Bool("RestartReminderWithText", newValue) }
|
||||||
|
}
|
||||||
|
static var WithBadge: Bool {
|
||||||
|
get { PrefsShared.Bool("RestartReminderWithBadge") }
|
||||||
|
set { PrefsShared.Bool("RestartReminderWithBadge", newValue) }
|
||||||
|
}
|
||||||
|
static var Sound: String {
|
||||||
|
get { PrefsShared.Str("RestartReminderSound") ?? "#default" }
|
||||||
|
set { PrefsShared.Str("RestartReminderSound", newValue) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enum ConnectionAlerts {
|
||||||
|
static var Enabled: Bool {
|
||||||
|
get { PrefsShared.Bool("ConnectionAlertsEnabled") }
|
||||||
|
set { PrefsShared.Bool("ConnectionAlertsEnabled", newValue) }
|
||||||
|
}
|
||||||
|
static var Sound: String {
|
||||||
|
get { PrefsShared.Str("ConnectionAlertsSound") ?? "#default" }
|
||||||
|
set { PrefsShared.Str("ConnectionAlertsSound", newValue) }
|
||||||
|
}
|
||||||
|
static var ExcludeMode: Bool {
|
||||||
|
get { PrefsShared.Bool("ConnectionAlertsExcludeMode") }
|
||||||
|
set { PrefsShared.Bool("ConnectionAlertsExcludeMode", newValue) }
|
||||||
|
}
|
||||||
|
enum Lists {
|
||||||
|
static var CustomA: Bool {
|
||||||
|
get { PrefsShared.Bool("ConnectionAlertsListsCustomA") }
|
||||||
|
set { PrefsShared.Bool("ConnectionAlertsListsCustomA", newValue) }
|
||||||
|
}
|
||||||
|
static var CustomB: Bool {
|
||||||
|
get { PrefsShared.Bool("ConnectionAlertsListsCustomB") }
|
||||||
|
set { PrefsShared.Bool("ConnectionAlertsListsCustomB", newValue) }
|
||||||
|
}
|
||||||
|
static var Blocked: Bool {
|
||||||
|
get { PrefsShared.Bool("ConnectionAlertsListsBlocked") }
|
||||||
|
set { PrefsShared.Bool("ConnectionAlertsListsBlocked", newValue) }
|
||||||
|
}
|
||||||
|
static var Else: Bool {
|
||||||
|
get { PrefsShared.Bool("ConnectionAlertsListsElse") }
|
||||||
|
set { PrefsShared.Bool("ConnectionAlertsListsElse", newValue) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,25 @@ import UIKit
|
|||||||
|
|
||||||
struct QuickUI {
|
struct QuickUI {
|
||||||
|
|
||||||
|
static func label(_ str: String, frame: CGRect = CGRect.zero, align: NSTextAlignment = .natural, style: UIFont.TextStyle = .body) -> UILabel {
|
||||||
|
let x = UILabel(frame: frame)
|
||||||
|
x.text = str
|
||||||
|
x.textAlignment = align
|
||||||
|
x.font = .preferredFont(forTextStyle: style)
|
||||||
|
x.constrainHuggingCompression(.horizontal, .defaultLow)
|
||||||
|
x.constrainHuggingCompression(.vertical, .defaultHigh)
|
||||||
|
x.sizeToFit()
|
||||||
|
if #available(iOS 10.0, *) {
|
||||||
|
x.adjustsFontForContentSizeCategory = true
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
static func button(_ title: String, target: Any? = nil, action: Selector? = nil) -> UIButton {
|
static func button(_ title: String, target: Any? = nil, action: Selector? = nil) -> UIButton {
|
||||||
let x = UIButton(type: .roundedRect)
|
let x = UIButton(type: .roundedRect)
|
||||||
x.setTitle(title, for: .normal)
|
x.setTitle(title, for: .normal)
|
||||||
x.titleLabel?.font = .preferredFont(forTextStyle: .body)
|
x.titleLabel?.font = .preferredFont(forTextStyle: .body)
|
||||||
|
x.constrainHuggingCompression(.vertical, .defaultHigh)
|
||||||
x.sizeToFit()
|
x.sizeToFit()
|
||||||
if let a = action { x.addTarget(target, action: a, for: .touchUpInside) }
|
if let a = action { x.addTarget(target, action: a, for: .touchUpInside) }
|
||||||
if #available(iOS 10.0, *) {
|
if #available(iOS 10.0, *) {
|
||||||
|
|||||||
@@ -33,6 +33,13 @@ class SearchBarManager: NSObject, UISearchResultsUpdating {
|
|||||||
if #available(iOS 11.0, *) {
|
if #available(iOS 11.0, *) {
|
||||||
tvc?.navigationItem.searchController = controller
|
tvc?.navigationItem.searchController = controller
|
||||||
} else {
|
} else {
|
||||||
|
let thv = tvc?.tableView.tableHeaderView
|
||||||
|
guard thv == nil || thv is UISearchBar else {
|
||||||
|
// Don't overwrite actions bar (co-occurrence, etc.)
|
||||||
|
// FIXME: find alternative or iOS 9-10 users can't search in hosts
|
||||||
|
tvc = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
controller.loadViewIfNeeded() // Fix: "Attempting to load the view of a view controller while it is deallocating"
|
controller.loadViewIfNeeded() // Fix: "Attempting to load the view of a view controller while it is deallocating"
|
||||||
tvc?.definesPresentationContext = true // make search bar disappear if user changes scene (eg. select cell)
|
tvc?.definesPresentationContext = true // make search bar disappear if user changes scene (eg. select cell)
|
||||||
//tvc?.tableView.backgroundView = UIView() // iOS 11+ bug: bright white background in dark mode
|
//tvc?.tableView.backgroundView = UIView() // iOS 11+ bug: bright white background in dark mode
|
||||||
@@ -42,7 +49,7 @@ class SearchBarManager: NSObject, UISearchResultsUpdating {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Search callback
|
/// Search callback
|
||||||
func updateSearchResults(for controller: UISearchController) {
|
internal func updateSearchResults(for controller: UISearchController) {
|
||||||
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(performSearch), object: nil)
|
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(performSearch), object: nil)
|
||||||
perform(#selector(performSearch), with: nil, afterDelay: 0.2)
|
perform(#selector(performSearch), with: nil, afterDelay: 0.2)
|
||||||
}
|
}
|
||||||
|
|||||||
182
main/Common Classes/SlideInAnimation.swift
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
import UIKit
|
||||||
|
|
||||||
|
enum PresentationEdge { case left, top, right, bottom }
|
||||||
|
|
||||||
|
// ########################################
|
||||||
|
// #
|
||||||
|
// # MARK: - Transitioning Delegate
|
||||||
|
// #
|
||||||
|
// ########################################
|
||||||
|
|
||||||
|
class SlideInTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
|
||||||
|
private var edge: PresentationEdge
|
||||||
|
private var modal: Bool
|
||||||
|
private var dismissable: Bool
|
||||||
|
private var shadow: UIColor?
|
||||||
|
|
||||||
|
init(for edge: PresentationEdge, modal: Bool, tapAnywhereToDismiss: Bool = false, modalBackgroundColor color: UIColor? = nil) {
|
||||||
|
self.edge = edge
|
||||||
|
self.dismissable = tapAnywhereToDismiss
|
||||||
|
self.shadow = color
|
||||||
|
self.modal = modal
|
||||||
|
}
|
||||||
|
|
||||||
|
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
|
||||||
|
StickyPresentationController(presented: presented, presenting: presenting, stickTo: edge, modal: modal, tapAnywhereToDismiss: dismissable, modalBackgroundColor: shadow)
|
||||||
|
}
|
||||||
|
|
||||||
|
func animationController(forPresented _: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||||
|
SlideInAnimationController(from: edge, isPresentation: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func animationController(forDismissed _: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||||
|
SlideInAnimationController(from: edge, isPresentation: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ########################################
|
||||||
|
// #
|
||||||
|
// # MARK: - Animated Transitioning
|
||||||
|
// #
|
||||||
|
// ########################################
|
||||||
|
|
||||||
|
private final class SlideInAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
|
||||||
|
let edge: PresentationEdge
|
||||||
|
let appear: Bool
|
||||||
|
|
||||||
|
init(from edge: PresentationEdge, isPresentation: Bool) {
|
||||||
|
self.edge = edge
|
||||||
|
self.appear = isPresentation
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func transitionDuration(using context: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||||
|
(context?.isAnimated ?? true) ? 0.3 : 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateTransition(using context: UIViewControllerContextTransitioning) {
|
||||||
|
guard let vc = context.viewController(forKey: appear ? .to : .from) else { return }
|
||||||
|
|
||||||
|
var to = context.finalFrame(for: vc)
|
||||||
|
var from = to
|
||||||
|
switch edge {
|
||||||
|
case .left: from.origin.x = -to.width
|
||||||
|
case .right: from.origin.x = context.containerView.frame.width
|
||||||
|
case .top: from.origin.y = -to.height
|
||||||
|
case .bottom: from.origin.y = context.containerView.frame.height
|
||||||
|
}
|
||||||
|
|
||||||
|
if appear { context.containerView.addSubview(vc.view) }
|
||||||
|
else { swap(&from, &to) }
|
||||||
|
|
||||||
|
vc.view.frame = from
|
||||||
|
UIView.animate(withDuration: transitionDuration(using: context), animations: {
|
||||||
|
vc.view.frame = to
|
||||||
|
}, completion: { finished in
|
||||||
|
if !self.appear { vc.view.removeFromSuperview() }
|
||||||
|
context.completeTransition(finished)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #########################################
|
||||||
|
// #
|
||||||
|
// # MARK: - Presentation Controller
|
||||||
|
// #
|
||||||
|
// #########################################
|
||||||
|
|
||||||
|
private class StickyPresentationController: UIPresentationController {
|
||||||
|
private let stickTo: PresentationEdge
|
||||||
|
private let isModal: Bool
|
||||||
|
|
||||||
|
private let bg = UIView()
|
||||||
|
private var availableSize: CGSize = .zero // save original size when resizing the container
|
||||||
|
|
||||||
|
override var shouldPresentInFullscreen: Bool { false }
|
||||||
|
override var frameOfPresentedViewInContainerView: CGRect { fittedContentFrame() }
|
||||||
|
|
||||||
|
required init(presented: UIViewController, presenting: UIViewController?, stickTo edge: PresentationEdge, modal: Bool = true, tapAnywhereToDismiss: Bool = false, modalBackgroundColor bgColor: UIColor? = nil) {
|
||||||
|
self.stickTo = edge
|
||||||
|
self.isModal = modal
|
||||||
|
super.init(presentedViewController: presented, presenting: presenting)
|
||||||
|
bg.backgroundColor = bgColor ?? .init(white: 0, alpha: 0.5)
|
||||||
|
if modal, tapAnywhereToDismiss {
|
||||||
|
bg.addGestureRecognizer(
|
||||||
|
UITapGestureRecognizer(target: self, action: #selector(didTapBackground))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Present
|
||||||
|
|
||||||
|
override func presentationTransitionWillBegin() {
|
||||||
|
availableSize = containerView!.frame.size
|
||||||
|
|
||||||
|
guard isModal else { return }
|
||||||
|
containerView!.insertSubview(bg, at: 0)
|
||||||
|
bg.alpha = 0.0
|
||||||
|
if presentedViewController.transitionCoordinator?.animate(alongsideTransition: { _ in
|
||||||
|
self.bg.alpha = 1.0
|
||||||
|
}) != true { bg.alpha = 1.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func didTapBackground(_ sender: UITapGestureRecognizer) {
|
||||||
|
presentingViewController.dismiss(animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Dismiss
|
||||||
|
|
||||||
|
override func dismissalTransitionWillBegin() {
|
||||||
|
if presentedViewController.transitionCoordinator?.animate(alongsideTransition: { _ in
|
||||||
|
self.bg.alpha = 0.0
|
||||||
|
}) != true { bg.alpha = 0.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
override func dismissalTransitionDidEnd(_ completed: Bool) {
|
||||||
|
if completed { bg.removeFromSuperview() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Update
|
||||||
|
|
||||||
|
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||||
|
availableSize = size
|
||||||
|
super.viewWillTransition(to: size, with: coordinator)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func containerViewDidLayoutSubviews() {
|
||||||
|
super.containerViewDidLayoutSubviews()
|
||||||
|
bg.frame = containerView!.bounds
|
||||||
|
if isModal {
|
||||||
|
presentedView!.frame = fittedContentFrame()
|
||||||
|
} else {
|
||||||
|
containerView!.frame = fittedContentFrame()
|
||||||
|
presentedView!.frame = containerView!.bounds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate `fittedContentSize()` then offset frame to sticky edge respecting *available* container size .
|
||||||
|
func fittedContentFrame() -> CGRect {
|
||||||
|
var frame = CGRect(origin: .zero, size: fittedContentSize())
|
||||||
|
switch stickTo {
|
||||||
|
case .right: frame.origin.x = availableSize.width - frame.width
|
||||||
|
case .bottom: frame.origin.y = availableSize.height - frame.height
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
return frame
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate best fitting size for available container size and presentation sticky edge.
|
||||||
|
func fittedContentSize() -> CGSize {
|
||||||
|
guard let target = presentedView else { return availableSize }
|
||||||
|
let full = availableSize
|
||||||
|
let preferred = presentedViewController.preferredContentSize
|
||||||
|
switch stickTo {
|
||||||
|
case .left, .right:
|
||||||
|
let fitted = target.fittingSize(fixedHeight: full.height, preferredWidth: preferred.width)
|
||||||
|
return CGSize(width: min(fitted.width, full.width), height: full.height)
|
||||||
|
case .top, .bottom:
|
||||||
|
let fitted = target.fittingSize(fixedWidth: full.width, preferredHeight: preferred.height)
|
||||||
|
return CGSize(width: full.width, height: min(fitted.height, full.height))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
main/Common Classes/ThrottledBatchQueue.swift
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
class ThrottledBatchQueue<T> {
|
||||||
|
private var cache: [T] = []
|
||||||
|
private var scheduled: Bool = false
|
||||||
|
private let queue: DispatchQueue
|
||||||
|
private let delay: Double
|
||||||
|
|
||||||
|
init(_ delay: Double, using queue: DispatchQueue) {
|
||||||
|
self.queue = queue
|
||||||
|
self.delay = delay
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDelayed(_ elem: T, afterDelay closure: @escaping ([T]) -> Void) {
|
||||||
|
queue.sync {
|
||||||
|
cache.append(elem)
|
||||||
|
guard !scheduled else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scheduled = true
|
||||||
|
queue.asyncAfter(deadline: .now() + delay) {
|
||||||
|
let aCopy = self.cache
|
||||||
|
self.cache.removeAll(keepingCapacity: true)
|
||||||
|
self.scheduled = false
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
closure(aCopy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
68
main/Common Classes/TinyMarkdown.swift
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import UIKit
|
||||||
|
|
||||||
|
struct TinyMarkdown {
|
||||||
|
/// Load markdown file and run through a (very) simple parser (see below).
|
||||||
|
/// - Parameters:
|
||||||
|
/// - filename: Will automatically append `.md` extension
|
||||||
|
/// - replacements: Replace a single occurrence of search string with an attributed replacement.
|
||||||
|
static func load(_ filename: String, replacements: [String : NSMutableAttributedString] = [:]) -> UITextView {
|
||||||
|
let url = Bundle.main.url(forResource: filename, withExtension: "md")!
|
||||||
|
let str = NSMutableAttributedString(withMarkdown: try! String(contentsOf: url))
|
||||||
|
for (key, val) in replacements {
|
||||||
|
guard let r = str.string.range(of: key) else {
|
||||||
|
QLog.Debug("WARN: markdown key '\(key)' does not exist in \(filename)")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
str.replaceCharacters(in: NSRange(r, in: str.string), with: val)
|
||||||
|
}
|
||||||
|
return QuickUI.text(attributed: str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NSMutableAttributedString {
|
||||||
|
/// Supports only: `#h1`, `##h2`, `###h3`, `_italic_`, `__bold__`, `___boldItalic___`
|
||||||
|
convenience init(withMarkdown content: String) {
|
||||||
|
self.init()
|
||||||
|
let emph = try! NSRegularExpression(pattern: #"(?<=(^|\W))(_{1,3})(\S|\S.*?\S)\2"#, options: [])
|
||||||
|
beginEditing()
|
||||||
|
content.enumerateLines { (line, _) in
|
||||||
|
if line.starts(with: "#") {
|
||||||
|
var h = 0
|
||||||
|
for char in line {
|
||||||
|
if char == "#" { h += 1 }
|
||||||
|
else { break }
|
||||||
|
}
|
||||||
|
var line = line
|
||||||
|
line.removeFirst(h)
|
||||||
|
line = line.trimmingCharacters(in: CharacterSet(charactersIn: " "))
|
||||||
|
switch h {
|
||||||
|
case 1: self.h1(line + "\n")
|
||||||
|
case 2: self.h2(line + "\n")
|
||||||
|
default: self.h3(line + "\n")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let nsline = line as NSString
|
||||||
|
let range = NSRange(location: 0, length: nsline.length)
|
||||||
|
var i = 0
|
||||||
|
for x in emph.matches(in: line, options: [], range: range) {
|
||||||
|
let r = x.range
|
||||||
|
self.normal(nsline.substring(from: i, to: r.location))
|
||||||
|
i = r.upperBound
|
||||||
|
let before = nsline.substring(with: r)
|
||||||
|
let after = before.trimmingCharacters(in: CharacterSet(charactersIn: "_"))
|
||||||
|
switch (before.count - after.count) / 2 {
|
||||||
|
case 1: self.italic(after)
|
||||||
|
case 2: self.bold(after)
|
||||||
|
default: self.boldItalic(after)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i < range.length {
|
||||||
|
self.normal(nsline.substring(from: i, to: range.length) + "\n")
|
||||||
|
} else {
|
||||||
|
self.normal("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endEditing()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ class TutorialSheet: UIViewController, UIScrollViewDelegate {
|
|||||||
private let sheetBg: UIView = {
|
private let sheetBg: UIView = {
|
||||||
let x = UIView(frame: uniRect)
|
let x = UIView(frame: uniRect)
|
||||||
x.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
x.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||||
x.backgroundColor = .sysBg
|
x.backgroundColor = .sysBackground
|
||||||
x.layer.cornerRadius = cornerRadius
|
x.layer.cornerRadius = cornerRadius
|
||||||
x.layer.shadowColor = UIColor.black.cgColor
|
x.layer.shadowColor = UIColor.black.cgColor
|
||||||
x.layer.shadowRadius = 10
|
x.layer.shadowRadius = 10
|
||||||
@@ -37,8 +37,8 @@ class TutorialSheet: UIViewController, UIScrollViewDelegate {
|
|||||||
private let pager: UIPageControl = {
|
private let pager: UIPageControl = {
|
||||||
let x = UIPageControl(frame: uniRect)
|
let x = UIPageControl(frame: uniRect)
|
||||||
x.frame.size.height = x.size(forNumberOfPages: 1).height
|
x.frame.size.height = x.size(forNumberOfPages: 1).height
|
||||||
x.currentPageIndicatorTintColor = UIColor.sysFg.withAlphaComponent(0.5)
|
x.currentPageIndicatorTintColor = UIColor.sysLabel.withAlphaComponent(0.5)
|
||||||
x.pageIndicatorTintColor = UIColor.sysFg.withAlphaComponent(0.25)
|
x.pageIndicatorTintColor = UIColor.sysLabel.withAlphaComponent(0.25)
|
||||||
x.numberOfPages = 0
|
x.numberOfPages = 0
|
||||||
x.hidesForSinglePage = true
|
x.hidesForSinglePage = true
|
||||||
x.addTarget(self, action: #selector(pagerDidChange), for: .valueChanged)
|
x.addTarget(self, action: #selector(pagerDidChange), for: .valueChanged)
|
||||||
@@ -59,7 +59,7 @@ class TutorialSheet: UIViewController, UIScrollViewDelegate {
|
|||||||
return x
|
return x
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private let button: UIButton = {
|
private lazy var button: UIButton = {
|
||||||
let x = QuickUI.button("", target: self, action: #selector(buttonTapped))
|
let x = QuickUI.button("", target: self, action: #selector(buttonTapped))
|
||||||
x.contentEdgeInsets = UIEdgeInsets(all: 8)
|
x.contentEdgeInsets = UIEdgeInsets(all: 8)
|
||||||
return x
|
return x
|
||||||
@@ -132,9 +132,9 @@ class TutorialSheet: UIViewController, UIScrollViewDelegate {
|
|||||||
sheetBg.addSubview(button)
|
sheetBg.addSubview(button)
|
||||||
|
|
||||||
pager.anchor([.top, .left, .right], to: sheetBg)
|
pager.anchor([.top, .left, .right], to: sheetBg)
|
||||||
pageScroll.topAnchor =&= pager.bottomAnchor
|
pageScroll.topAnchor =&= pager.bottomAnchor | .defaultHigh
|
||||||
pageScroll.anchor([.left, .right, .top, .bottom], to: sheetBg, margin: sheetInset) | .defaultHigh
|
pageScroll.anchor([.left, .right, .top, .bottom], to: sheetBg, margin: sheetInset) | .defaultHigh
|
||||||
button.topAnchor =&= pageScroll.bottomAnchor
|
button.topAnchor =&= pageScroll.bottomAnchor | .defaultHigh
|
||||||
button.anchor([.bottom, .centerX], to: sheetBg)
|
button.anchor([.bottom, .centerX], to: sheetBg)
|
||||||
// button.bottomAnchor =&= sheetBg.bottomAnchor - 30
|
// button.bottomAnchor =&= sheetBg.bottomAnchor - 30
|
||||||
// button.centerXAnchor =&= sheetBg.centerXAnchor
|
// button.centerXAnchor =&= sheetBg.centerXAnchor
|
||||||
|
|||||||
@@ -20,27 +20,19 @@ extension SQLiteDatabase {
|
|||||||
try ifStep(stmt, SQLITE_ROW)
|
try ifStep(stmt, SQLITE_ROW)
|
||||||
return sqlite3_column_int(stmt, 0)
|
return sqlite3_column_int(stmt, 0)
|
||||||
}
|
}
|
||||||
if version != 1 {
|
if version != 2 {
|
||||||
|
QLog.Info("migrate db \(version) -> 2")
|
||||||
// version 0 -> 1: req(domain) -> heap(fqdn, domain)
|
// version 0 -> 1: req(domain) -> heap(fqdn, domain)
|
||||||
if version == 0 {
|
// version 1 -> 2: rec(+subtitle, +opt)
|
||||||
try tempMigrate()
|
if version == 1 {
|
||||||
|
transaction("""
|
||||||
|
ALTER TABLE rec ADD COLUMN subtitle TEXT;
|
||||||
|
ALTER TABLE rec ADD COLUMN uploadkey TEXT;
|
||||||
|
""")
|
||||||
}
|
}
|
||||||
try run(sql: "PRAGMA user_version = 1;")
|
try run(sql: "PRAGMA user_version = 2;")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func tempMigrate() throws { // TODO: remove with next internal release
|
|
||||||
do {
|
|
||||||
try run(sql: "SELECT 1 FROM req LIMIT 1;") // fails if req doesnt exist
|
|
||||||
createFunction("domainof") { ($0.first as! String).extractDomain() }
|
|
||||||
try run(sql: """
|
|
||||||
BEGIN TRANSACTION;
|
|
||||||
INSERT INTO heap(ts,fqdn,domain,opt) SELECT ts,domain,domainof(domain),nullif(logOpt,0) FROM req;
|
|
||||||
DROP TABLE req;
|
|
||||||
COMMIT;
|
|
||||||
""")
|
|
||||||
} catch { /* no need to migrate */ }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum TableName: String {
|
private enum TableName: String {
|
||||||
@@ -54,6 +46,10 @@ extension SQLiteDatabase {
|
|||||||
return sqlite3_column_int64($0, 0)
|
return sqlite3_column_int64($0, 0)
|
||||||
}) ?? 0
|
}) ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileprivate func col_ts(_ stmt: OpaquePointer, _ col: Int32) -> Timestamp {
|
||||||
|
sqlite3_column_int64(stmt, col)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class WhereClauseBuilder: CustomStringConvertible {
|
class WhereClauseBuilder: CustomStringConvertible {
|
||||||
@@ -105,6 +101,7 @@ struct GroupedDomain {
|
|||||||
var options: FilterOptions? = nil
|
var options: FilterOptions? = nil
|
||||||
}
|
}
|
||||||
typealias GroupedTsOccurrence = (ts: Timestamp, total: Int32, blocked: Int32)
|
typealias GroupedTsOccurrence = (ts: Timestamp, total: Int32, blocked: Int32)
|
||||||
|
typealias DomainTsPair = (domain: String, ts: Timestamp)
|
||||||
|
|
||||||
extension SQLiteDatabase {
|
extension SQLiteDatabase {
|
||||||
|
|
||||||
@@ -116,11 +113,9 @@ extension SQLiteDatabase {
|
|||||||
guard lastRowId(.cache) > 0 else { return nil }
|
guard lastRowId(.cache) > 0 else { return nil }
|
||||||
let before = lastRowId(.heap) + 1
|
let before = lastRowId(.heap) + 1
|
||||||
createFunction("domainof") { ($0.first as! String).extractDomain() }
|
createFunction("domainof") { ($0.first as! String).extractDomain() }
|
||||||
try? run(sql:"""
|
transaction("""
|
||||||
BEGIN TRANSACTION;
|
|
||||||
INSERT INTO heap(ts,fqdn,domain,opt) SELECT ts,dns,domainof(dns),nullif(opt&1,0) FROM cache;
|
INSERT INTO heap(ts,fqdn,domain,opt) SELECT ts,dns,domainof(dns),nullif(opt&1,0) FROM cache;
|
||||||
DELETE FROM cache;
|
DELETE FROM cache;
|
||||||
COMMIT;
|
|
||||||
""")
|
""")
|
||||||
let after = lastRowId(.heap)
|
let after = lastRowId(.heap)
|
||||||
return (before > after) ? nil : (before, after)
|
return (before > after) ? nil : (before, after)
|
||||||
@@ -150,7 +145,7 @@ extension SQLiteDatabase {
|
|||||||
func dnsLogsMinDate() -> Timestamp? {
|
func dnsLogsMinDate() -> Timestamp? {
|
||||||
try? run(sql:"SELECT min(ts) FROM heap") {
|
try? run(sql:"SELECT min(ts) FROM heap") {
|
||||||
try ifStep($0, SQLITE_ROW)
|
try ifStep($0, SQLITE_ROW)
|
||||||
return sqlite3_column_int64($0, 0)
|
return col_ts($0, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,8 +159,19 @@ extension SQLiteDatabase {
|
|||||||
let Where = WhereClauseBuilder().and(in: range).and(min: ts1, max: ts2)
|
let Where = WhereClauseBuilder().and(in: range).and(min: ts1, max: ts2)
|
||||||
return try? run(sql:"SELECT min(rowid), max(rowid) FROM heap \(Where);", bind: Where.bindings) {
|
return try? run(sql:"SELECT min(rowid), max(rowid) FROM heap \(Where);", bind: Where.bindings) {
|
||||||
try ifStep($0, SQLITE_ROW)
|
try ifStep($0, SQLITE_ROW)
|
||||||
let max = sqlite3_column_int64($0, 1)
|
let max = col_ts($0, 1)
|
||||||
return (max == 0) ? nil : (sqlite3_column_int64($0, 0), max)
|
return (max == 0) ? nil : (col_ts($0, 0), max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get raw logs between two timestamps. `ts >= ? AND ts <= ?`
|
||||||
|
/// - Returns: List sorted by `ts` in descending order (newest entries first).
|
||||||
|
func dnsLogs(between ts1: Timestamp, and ts2: Timestamp) -> [DomainTsPair]? {
|
||||||
|
try? run(sql: "SELECT fqdn, ts FROM heap WHERE ts >= ? AND ts <= ? ORDER BY ts DESC, rowid ASC;",
|
||||||
|
bind: [BindInt64(ts1), BindInt64(ts2)]) {
|
||||||
|
allRows($0) {
|
||||||
|
(col_text($0, 0) ?? "", col_ts($0, 1))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,10 +195,10 @@ extension SQLiteDatabase {
|
|||||||
}
|
}
|
||||||
return try? run(sql: "SELECT \(col), COUNT(*), COUNT(opt), MAX(ts) FROM heap \(Where) GROUP BY \(col);", bind: Where.bindings) {
|
return try? run(sql: "SELECT \(col), COUNT(*), COUNT(opt), MAX(ts) FROM heap \(Where) GROUP BY \(col);", bind: Where.bindings) {
|
||||||
allRows($0) {
|
allRows($0) {
|
||||||
GroupedDomain(domain: readText($0, 0) ?? "",
|
GroupedDomain(domain: col_text($0, 0) ?? "",
|
||||||
total: sqlite3_column_int($0, 1),
|
total: sqlite3_column_int($0, 1),
|
||||||
blocked: sqlite3_column_int($0, 2),
|
blocked: sqlite3_column_int($0, 2),
|
||||||
lastModified: sqlite3_column_int64($0, 3))
|
lastModified: col_ts($0, 3))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,7 +212,7 @@ extension SQLiteDatabase {
|
|||||||
let Where = WhereClauseBuilder().and(in: range).and("fqdn = ?", BindText(fqdn))
|
let Where = WhereClauseBuilder().and(in: range).and("fqdn = ?", BindText(fqdn))
|
||||||
return try? run(sql: "SELECT ts, COUNT(ts), COUNT(opt) FROM heap \(Where) GROUP BY ts ORDER BY ts DESC;", bind: Where.bindings) {
|
return try? run(sql: "SELECT ts, COUNT(ts), COUNT(opt) FROM heap \(Where) GROUP BY ts ORDER BY ts DESC;", bind: Where.bindings) {
|
||||||
allRows($0) {
|
allRows($0) {
|
||||||
(sqlite3_column_int64($0, 0), sqlite3_column_int($0, 1), sqlite3_column_int($0, 2))
|
(col_ts($0, 0), sqlite3_column_int($0, 1), sqlite3_column_int($0, 2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,9 +234,10 @@ extension SQLiteDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get sorted, unique list of `ts` with given `fqdn`.
|
/// Get sorted, unique list of `ts` with given `fqdn`.
|
||||||
func dnsLogsUniqTs(_ fqdn: String) -> [Timestamp]? {
|
func dnsLogsUniqTs(_ domain: String, isFQDN flag: Bool) -> [Timestamp]? {
|
||||||
try? run(sql: "SELECT DISTINCT ts FROM heap WHERE fqdn = ? ORDER BY ts;", bind: [BindText(fqdn)]) {
|
try? run(sql: "SELECT DISTINCT ts FROM heap WHERE \(flag ? "fqdn" : "domain") = ? ORDER BY ts;",
|
||||||
allRows($0) { sqlite3_column_int64($0, 0) }
|
bind: [BindText(domain)]) {
|
||||||
|
allRows($0) { col_ts($0, 0) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +248,7 @@ extension SQLiteDatabase {
|
|||||||
/// - dt: Search for `ts - dt <= X <= ts + dt`
|
/// - dt: Search for `ts - dt <= X <= ts + dt`
|
||||||
/// - fqdn: Rows matching this domain will be excluded from the result set.
|
/// - fqdn: Rows matching this domain will be excluded from the result set.
|
||||||
/// - Returns: List of tuples ordered by rank (ASC).
|
/// - Returns: List of tuples ordered by rank (ASC).
|
||||||
func contextAnalysis(coOccurrence times: [Timestamp], plusMinus dt: Timestamp, exclude fqdn: String) -> [ContextAnalysisResult]? {
|
func contextAnalysis(coOccurrence times: [Timestamp], plusMinus dt: Timestamp, exclude domain: String, isFQDN flag: Bool) -> [ContextAnalysisResult]? {
|
||||||
guard times.count > 0 else { return nil }
|
guard times.count > 0 else { return nil }
|
||||||
createFunction("fnDist") {
|
createFunction("fnDist") {
|
||||||
let x = $0.first as! Timestamp
|
let x = $0.first as! Timestamp
|
||||||
@@ -266,12 +273,12 @@ extension SQLiteDatabase {
|
|||||||
SELECT fqdn, count, avg, (\(fnRank)) rank FROM (
|
SELECT fqdn, count, avg, (\(fnRank)) rank FROM (
|
||||||
SELECT fqdn, COUNT(*) count, AVG(dist) avg FROM (
|
SELECT fqdn, COUNT(*) count, AVG(dist) avg FROM (
|
||||||
SELECT fqdn, fnDist(ts) dist FROM heap
|
SELECT fqdn, fnDist(ts) dist FROM heap
|
||||||
WHERE ts BETWEEN ? AND ? AND fqdn != ? AND dist <= ?
|
WHERE ts BETWEEN ? AND ? AND \(flag ? "fqdn" : "domain") != ? AND dist <= ?
|
||||||
) GROUP BY fqdn
|
) GROUP BY fqdn
|
||||||
) ORDER BY rank ASC LIMIT 99;
|
) ORDER BY rank ASC LIMIT 99;
|
||||||
""", bind: [BindInt64(dt), BindInt64(low), BindInt64(high), BindText(fqdn), BindInt64(dt)]) {
|
""", bind: [BindInt64(dt), BindInt64(low), BindInt64(high), BindText(domain), BindInt64(dt)]) {
|
||||||
allRows($0) {
|
allRows($0) {
|
||||||
(readText($0, 0) ?? "", sqlite3_column_int($0, 1), sqlite3_column_double($0, 2), sqlite3_column_double($0, 3))
|
(col_text($0, 0) ?? "", sqlite3_column_int($0, 1), sqlite3_column_double($0, 2), sqlite3_column_double($0, 3))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -282,7 +289,7 @@ extension SQLiteDatabase {
|
|||||||
// MARK: - Recordings
|
// MARK: - Recordings
|
||||||
|
|
||||||
extension CreateTable {
|
extension CreateTable {
|
||||||
/// `id`: Primary, `start`: Timestamp, `stop`: Timestamp, `appid`: String, `title`: String, `notes`: String
|
/// `id`: Primary, `start`: Timestamp, `stop`: Timestamp, `appid`: String, `title`: String, `subtitle`: String, `notes`: String
|
||||||
static var rec: String {"""
|
static var rec: String {"""
|
||||||
CREATE TABLE IF NOT EXISTS rec(
|
CREATE TABLE IF NOT EXISTS rec(
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
@@ -290,19 +297,25 @@ extension CreateTable {
|
|||||||
stop INTEGER,
|
stop INTEGER,
|
||||||
appid TEXT,
|
appid TEXT,
|
||||||
title TEXT,
|
title TEXT,
|
||||||
notes TEXT
|
subtitle TEXT,
|
||||||
|
notes TEXT,
|
||||||
|
uploadkey TEXT
|
||||||
);
|
);
|
||||||
"""}
|
"""}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let readRecordingSelect = "id, start, stop, appid, title, subtitle, notes, uploadkey"
|
||||||
struct Recording {
|
struct Recording {
|
||||||
let id: sqlite3_int64
|
let id: sqlite3_int64
|
||||||
let start: Timestamp
|
let start: Timestamp
|
||||||
let stop: Timestamp?
|
let stop: Timestamp?
|
||||||
var appId: String? = nil
|
var appId: String? = nil
|
||||||
var title: String? = nil
|
var title: String? = nil
|
||||||
|
var subtitle: String? = nil
|
||||||
var notes: String? = nil
|
var notes: String? = nil
|
||||||
|
var uploadkey: String? = nil
|
||||||
}
|
}
|
||||||
|
typealias AppBundleInfo = (bundleId: String, name: String?, author: String?)
|
||||||
|
|
||||||
extension SQLiteDatabase {
|
extension SQLiteDatabase {
|
||||||
|
|
||||||
@@ -329,8 +342,9 @@ extension SQLiteDatabase {
|
|||||||
|
|
||||||
/// Update given recording by replacing `title`, `appid`, and `notes` with new values.
|
/// Update given recording by replacing `title`, `appid`, and `notes` with new values.
|
||||||
func recordingUpdate(_ r: Recording) {
|
func recordingUpdate(_ r: Recording) {
|
||||||
try? run(sql: "UPDATE rec SET title = ?, appid = ?, notes = ? WHERE id = ? LIMIT 1;",
|
try? run(sql: "UPDATE rec SET appid = ?, title = ?, subtitle = ?, notes = ?, uploadkey = ? WHERE id = ? LIMIT 1;",
|
||||||
bind: [BindTextOrNil(r.title), BindTextOrNil(r.appId), BindTextOrNil(r.notes), BindInt64(r.id)]) { stmt -> Void in
|
bind: [BindTextOrNil(r.appId), BindTextOrNil(r.title), BindTextOrNil(r.subtitle),
|
||||||
|
BindTextOrNil(r.notes), BindTextOrNil(r.uploadkey), BindInt64(r.id)]) { stmt -> Void in
|
||||||
sqlite3_step(stmt)
|
sqlite3_step(stmt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -348,37 +362,55 @@ extension SQLiteDatabase {
|
|||||||
// MARK: read
|
// MARK: read
|
||||||
|
|
||||||
private func readRecording(_ stmt: OpaquePointer) -> Recording {
|
private func readRecording(_ stmt: OpaquePointer) -> Recording {
|
||||||
let end = sqlite3_column_int64(stmt, 2)
|
let end = col_ts(stmt, 2)
|
||||||
return Recording(id: sqlite3_column_int64(stmt, 0),
|
return Recording(id: sqlite3_column_int64(stmt, 0),
|
||||||
start: sqlite3_column_int64(stmt, 1),
|
start: col_ts(stmt, 1),
|
||||||
stop: end == 0 ? nil : end,
|
stop: end == 0 ? nil : end,
|
||||||
appId: readText(stmt, 3),
|
appId: col_text(stmt, 3),
|
||||||
title: readText(stmt, 4),
|
title: col_text(stmt, 4),
|
||||||
notes: readText(stmt, 5))
|
subtitle: col_text(stmt, 5),
|
||||||
|
notes: col_text(stmt, 6),
|
||||||
|
uploadkey: col_text(stmt, 7))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `WHERE stop IS NULL`
|
/// `WHERE stop IS NULL`
|
||||||
func recordingGetOngoing() -> Recording? {
|
func recordingGetOngoing() -> Recording? {
|
||||||
try? run(sql: "SELECT * FROM rec WHERE stop IS NULL LIMIT 1;") {
|
try? run(sql: "SELECT \(readRecordingSelect) FROM rec WHERE stop IS NULL LIMIT 1;") {
|
||||||
try ifStep($0, SQLITE_ROW)
|
try ifStep($0, SQLITE_ROW)
|
||||||
return readRecording($0)
|
return readRecording($0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get `Timestamp` of last recording.
|
||||||
|
func recordingLastTimestamp() -> Timestamp? {
|
||||||
|
try? run(sql: "SELECT stop FROM rec WHERE stop IS NOT NULL ORDER BY rowid DESC LIMIT 1;") {
|
||||||
|
try ifStep($0, SQLITE_ROW)
|
||||||
|
return col_ts($0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// `WHERE stop IS NOT NULL`
|
/// `WHERE stop IS NOT NULL`
|
||||||
func recordingGetAll() -> [Recording]? {
|
func recordingGetAll() -> [Recording]? {
|
||||||
try? run(sql: "SELECT * FROM rec WHERE stop IS NOT NULL;") {
|
try? run(sql: "SELECT \(readRecordingSelect) FROM rec WHERE stop IS NOT NULL;") {
|
||||||
allRows($0) { readRecording($0) }
|
allRows($0) { readRecording($0) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `WHERE id = ?`
|
/// `WHERE id = ?`
|
||||||
private func recordingGet(withID: sqlite3_int64) throws -> Recording {
|
private func recordingGet(withID: sqlite3_int64) throws -> Recording {
|
||||||
try run(sql: "SELECT * FROM rec WHERE id = ? LIMIT 1;", bind: [BindInt64(withID)]) {
|
try run(sql: "SELECT \(readRecordingSelect) FROM rec WHERE id = ? LIMIT 1;", bind: [BindInt64(withID)]) {
|
||||||
try ifStep($0, SQLITE_ROW)
|
try ifStep($0, SQLITE_ROW)
|
||||||
return readRecording($0)
|
return readRecording($0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func appBundleList() -> [AppBundleInfo]? {
|
||||||
|
try? run(sql: "SELECT appid, title, subtitle FROM rec WHERE appid IS NOT NULL GROUP BY appid ORDER BY lower(title) ASC;") {
|
||||||
|
allRows($0) {
|
||||||
|
AppBundleInfo(col_text($0, 0)!, col_text($0, 1), col_text($0, 2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -396,8 +428,6 @@ extension CreateTable {
|
|||||||
"""}
|
"""}
|
||||||
}
|
}
|
||||||
|
|
||||||
typealias RecordLog = (domain: String, count: Int32)
|
|
||||||
|
|
||||||
extension SQLiteDatabase {
|
extension SQLiteDatabase {
|
||||||
|
|
||||||
// MARK: write
|
// MARK: write
|
||||||
@@ -426,13 +456,24 @@ extension SQLiteDatabase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Delete one recording log entry with given `recording id`, matching `domain`, and `ts`.
|
||||||
|
/// - Returns: `true` if row was deleted
|
||||||
|
func recordingLogsDelete(_ recId: sqlite3_int64, singleEntry ts: Timestamp, domain: String) throws -> Bool {
|
||||||
|
try run(sql: "DELETE FROM recLog WHERE rid = ? AND ts = ? AND domain = ? LIMIT 1;",
|
||||||
|
bind: [BindInt64(recId), BindInt64(ts), BindText(domain)]) {
|
||||||
|
try ifStep($0, SQLITE_DONE)
|
||||||
|
return numberOfChanges > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: read
|
// MARK: read
|
||||||
|
|
||||||
/// List of domains and count occurences for given recording.
|
/// List of domains and count occurences for given recording.
|
||||||
func recordingLogsGetGrouped(_ r: Recording) -> [RecordLog]? {
|
/// - Returns: List of `(domain, ts)` pairs. Sorted by `ts` in ascending order (oldest first)
|
||||||
try? run(sql: "SELECT domain, COUNT() FROM recLog WHERE rid = ? GROUP BY domain;",
|
func recordingLogsGet(_ r: Recording) -> [DomainTsPair]? {
|
||||||
|
try? run(sql: "SELECT domain, ts FROM recLog WHERE rid = ? ORDER BY ts ASC, rowid DESC;",
|
||||||
bind: [BindInt64(r.id)]) {
|
bind: [BindInt64(r.id)]) {
|
||||||
allRows($0) { (readText($0, 0) ?? "", sqlite3_column_int($0, 1)) }
|
allRows($0) { (col_text($0, 0) ?? "", col_ts($0, 1)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,9 +39,26 @@ 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 }
|
||||||
|
func delFrom(_ table: String) throws -> Bool {
|
||||||
|
return try self.run(sql: "DELETE FROM \(table) WHERE ts < strftime('%s', 'now', ?);",
|
||||||
|
bind: [BindText("-\(days) days")]) {
|
||||||
|
try ifStep($0, SQLITE_DONE)
|
||||||
|
return numberOfChanges > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let didDelHeap = try delFrom("heap")
|
||||||
|
let didDelCache = try delFrom("cache")
|
||||||
|
return didDelHeap || didDelCache
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -62,7 +79,9 @@ struct FilterOptions: OptionSet {
|
|||||||
static let none = FilterOptions([])
|
static let none = FilterOptions([])
|
||||||
static let blocked = FilterOptions(rawValue: 1 << 0)
|
static let blocked = FilterOptions(rawValue: 1 << 0)
|
||||||
static let ignored = FilterOptions(rawValue: 1 << 1)
|
static let ignored = FilterOptions(rawValue: 1 << 1)
|
||||||
static let any = FilterOptions(rawValue: 0b11)
|
static let customA = FilterOptions(rawValue: 1 << 2)
|
||||||
|
static let customB = FilterOptions(rawValue: 1 << 3)
|
||||||
|
static let any = FilterOptions(rawValue: 0b1111)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SQLiteDatabase {
|
extension SQLiteDatabase {
|
||||||
@@ -71,7 +90,7 @@ extension SQLiteDatabase {
|
|||||||
return try? run(sql: "SELECT domain, opt FROM filter \(rv>0 ? "WHERE opt & ?" : "");",
|
return try? run(sql: "SELECT domain, opt FROM filter \(rv>0 ? "WHERE opt & ?" : "");",
|
||||||
bind: rv>0 ? [BindInt32(rv)] : []) {
|
bind: rv>0 ? [BindInt32(rv)] : []) {
|
||||||
allRowsKeyed($0) {
|
allRowsKeyed($0) {
|
||||||
(key: readText($0, 0) ?? "",
|
(key: col_text($0, 0) ?? "",
|
||||||
value: FilterOptions(rawValue: sqlite3_column_int($0, 1)))
|
value: FilterOptions(rawValue: sqlite3_column_int($0, 1)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ class SQLiteDatabase {
|
|||||||
static func open(path: String = URL.internalDB().relativePath) throws -> SQLiteDatabase {
|
static func open(path: String = URL.internalDB().relativePath) throws -> SQLiteDatabase {
|
||||||
var db: OpaquePointer?
|
var db: OpaquePointer?
|
||||||
if sqlite3_open_v2(path, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, nil) == SQLITE_OK {
|
if sqlite3_open_v2(path, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, nil) == SQLITE_OK {
|
||||||
|
sqlite3_busy_timeout(db, 800)
|
||||||
return SQLiteDatabase(dbPointer: db)
|
return SQLiteDatabase(dbPointer: db)
|
||||||
} else {
|
} else {
|
||||||
defer { sqlite3_close_v2(db) }
|
defer { sqlite3_close_v2(db) }
|
||||||
@@ -91,15 +92,20 @@ class SQLiteDatabase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `BEGIN TRANSACTION; \(sql); COMMIT;` on exception rollback.
|
||||||
|
func transaction(_ sql: String) {
|
||||||
|
do { try run(sql: "BEGIN TRANSACTION; \(sql); COMMIT;") }
|
||||||
|
catch { rollback() }
|
||||||
|
}
|
||||||
|
|
||||||
func ifStep(_ stmt: OpaquePointer, _ expected: Int32) throws {
|
func ifStep(_ stmt: OpaquePointer, _ expected: Int32) throws {
|
||||||
guard sqlite3_step(stmt) == expected else {
|
guard sqlite3_step(stmt) == expected else {
|
||||||
throw SQLiteError.Step(message: errorMessage)
|
throw SQLiteError.Step(message: errorMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func vacuum() {
|
func vacuum() { NSLog("[SQL] VACUUM"); try? run(sql: "VACUUM;"); }
|
||||||
try? run(sql: "VACUUM;")
|
func rollback() { NSLog("[SQL] ROLLBACK"); try? run(sql: "ROLLBACK;"); }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -163,6 +169,10 @@ protocol DBBinding {
|
|||||||
func bind(_ stmt: OpaquePointer!, _ col: Int32) -> Int32
|
func bind(_ stmt: OpaquePointer!, _ col: Int32) -> Int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct BindNull : DBBinding {
|
||||||
|
func bind(_ stmt: OpaquePointer!, _ col: Int32) -> Int32 { sqlite3_bind_null(stmt, col) }
|
||||||
|
}
|
||||||
|
|
||||||
struct BindInt32 : DBBinding {
|
struct BindInt32 : DBBinding {
|
||||||
let raw: Int32
|
let raw: Int32
|
||||||
init(_ value: Int32) { raw = value }
|
init(_ value: Int32) { raw = value }
|
||||||
@@ -193,7 +203,7 @@ extension SQLiteDatabase {
|
|||||||
var numberOfChanges: Int32 { get { sqlite3_changes(dbPointer) } }
|
var numberOfChanges: Int32 { get { sqlite3_changes(dbPointer) } }
|
||||||
var lastInsertedRow: SQLiteRowID { get { sqlite3_last_insert_rowid(dbPointer) } }
|
var lastInsertedRow: SQLiteRowID { get { sqlite3_last_insert_rowid(dbPointer) } }
|
||||||
|
|
||||||
func readText(_ stmt: OpaquePointer, _ col: Int32) -> String? {
|
func col_text(_ stmt: OpaquePointer, _ col: Int32) -> String? {
|
||||||
let val = sqlite3_column_text(stmt, col)
|
let val = sqlite3_column_text(stmt, col)
|
||||||
return (val != nil ? String(cString: val!) : nil)
|
return (val != nil ? String(cString: val!) : nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,13 @@ extension FilterOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension Recording {
|
extension Recording {
|
||||||
var fallbackTitle: String { get { "Unnamed Recording #\(id)" } }
|
static let minTimeLongTerm: Timestamp = .hours(1)
|
||||||
var duration: Timestamp? { get { stop == nil ? nil : stop! - start } }
|
|
||||||
var durationString: String? { get { stop == nil ? nil : TimeFormat.from(duration!) } }
|
var fallbackTitle: String { get {
|
||||||
|
isLongTerm ? "Background Recording" : "Unnamed Recording #\(id)"
|
||||||
|
} }
|
||||||
|
var duration: Timestamp { get { (stop ?? .now()) - start } }
|
||||||
|
var isLongTerm: Bool { duration > Recording.minTimeLongTerm }
|
||||||
|
var isShared: Bool { uploadkey?.count ?? 0 > 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,21 @@ 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")
|
||||||
|
do {
|
||||||
|
if try AppDB!.dnsLogsDeleteOlderThan(days: days) {
|
||||||
|
sync.needsReloadDB()
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
QLog.Warning("Couldn't auto-delete logs, \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,13 @@ enum DomainFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get total number of blocked and ignored domains. Shown in settings overview.
|
/// Get total number of blocked and ignored domains. Shown in settings overview.
|
||||||
static func counts() -> (blocked: Int, ignored: Int) {
|
static func counts() -> (blocked: Int, ignored: Int, listCustomA: Int, listCustomB: Int) {
|
||||||
data.reduce(into: (0, 0)) {
|
data.reduce(into: (0, 0, 0, 0)) {
|
||||||
if $1.1.contains(.blocked) { $0.0 += 1 }
|
if $1.1.contains(.blocked) { $0.0 += 1 }
|
||||||
if $1.1.contains(.ignored) { $0.1 += 1 } }
|
if $1.1.contains(.ignored) { $0.1 += 1 }
|
||||||
|
if $1.1.contains(.customA) { $0.2 += 1 }
|
||||||
|
if $1.1.contains(.customB) { $0.3 += 1 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Union `filter` with set.
|
/// Union `filter` with set.
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ class GroupedDomainDataSource: FilterPipelineDelegate, SyncUpdateDelegate {
|
|||||||
/// Read user defaults and apply new sorting order. Either by setting a new or reversing the current.
|
/// Read user defaults and apply new sorting order. Either by setting a new or reversing the current.
|
||||||
/// - Parameter force: If `true` set new sorting even if the type does not differ.
|
/// - Parameter force: If `true` set new sorting even if the type does not differ.
|
||||||
private func resetSortingOrder(force: Bool = false) {
|
private func resetSortingOrder(force: Bool = false) {
|
||||||
let orderAscChanged = (orderAsc <-? Pref.DateFilter.OrderAsc)
|
let orderAscChanged = (orderAsc <-? Prefs.DateFilter.OrderAsc)
|
||||||
let orderTypChanged = (currentOrder <-? Pref.DateFilter.OrderBy)
|
let orderTypChanged = (currentOrder <-? Prefs.DateFilter.OrderBy)
|
||||||
if orderTypChanged || force {
|
if orderTypChanged || force {
|
||||||
switch currentOrder {
|
switch currentOrder {
|
||||||
case .Date:
|
case .Date:
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ enum RecordingsDB {
|
|||||||
/// Get list of all recordings
|
/// Get list of all recordings
|
||||||
static func list() -> [Recording] { AppDB?.recordingGetAll() ?? [] }
|
static func list() -> [Recording] { AppDB?.recordingGetAll() ?? [] }
|
||||||
|
|
||||||
|
/// Get `Timestamp` of latest recording
|
||||||
|
static func lastTimestamp() -> Timestamp? { AppDB?.recordingLastTimestamp() }
|
||||||
|
|
||||||
/// Copy log entries from generic `heap` table to recording specific `recLog` table
|
/// Copy log entries from generic `heap` table to recording specific `recLog` table
|
||||||
static func persist(_ r: Recording) {
|
static func persist(_ r: Recording) {
|
||||||
sync.syncNow { // persist changes in cache before copying recording details
|
sync.syncNow { // persist changes in cache before copying recording details
|
||||||
@@ -21,8 +24,20 @@ enum RecordingsDB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get list of domains that occured during the recording
|
/// Get list of domains that occured during the recording
|
||||||
static func details(_ r: Recording) -> [RecordLog] {
|
static func details(_ r: Recording) -> [DomainTsPair] {
|
||||||
AppDB?.recordingLogsGetGrouped(r) ?? []
|
AppDB?.recordingLogsGet(r) ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get dictionary of domains with `ts` in ascending order.
|
||||||
|
static func detailCluster(_ r: Recording) -> [String : [Timestamp]] {
|
||||||
|
var cluster: [String : [Timestamp]] = [:]
|
||||||
|
for (dom, ts) in details(r) {
|
||||||
|
if cluster[dom] == nil {
|
||||||
|
cluster[dom] = []
|
||||||
|
}
|
||||||
|
cluster[dom]!.append(ts - r.start)
|
||||||
|
}
|
||||||
|
return cluster
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update `title`, `appid`, and `notes` and post `NotifyRecordingChanged` notification.
|
/// Update `title`, `appid`, and `notes` and post `NotifyRecordingChanged` notification.
|
||||||
@@ -43,5 +58,16 @@ enum RecordingsDB {
|
|||||||
static func deleteDetails(_ r: Recording, domain: String) -> Bool {
|
static func deleteDetails(_ r: Recording, domain: String) -> Bool {
|
||||||
((try? AppDB?.recordingLogsDelete(r.id, matchingDomain: domain)) ?? 0) > 0
|
((try? AppDB?.recordingLogsDelete(r.id, matchingDomain: domain)) ?? 0) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Delete individual entries from recording while keeping the recording alive.
|
||||||
|
/// - Returns: `true` if at least one row is deleted.
|
||||||
|
static func deleteSingle(_ r: Recording, domain: String, ts: Timestamp) -> Bool {
|
||||||
|
(try? AppDB?.recordingLogsDelete(r.id, singleEntry: ts, domain: domain)) ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return list of previously used apps found in all recordings.
|
||||||
|
static func appList() -> [AppBundleInfo] {
|
||||||
|
AppDB?.appBundleList() ?? []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import Foundation
|
|||||||
|
|
||||||
#if IOS_SIMULATOR
|
#if IOS_SIMULATOR
|
||||||
|
|
||||||
class TestDataSource {
|
fileprivate var hook : GlassVPNHook!
|
||||||
|
|
||||||
|
class SimulatorVPN {
|
||||||
|
static var timer: Timer?
|
||||||
|
|
||||||
static func load() {
|
static func load() {
|
||||||
QLog.Debug("SQLite path: \(URL.internalDB())")
|
QLog.Debug("SQLite path: \(URL.internalDB())")
|
||||||
@@ -27,13 +30,36 @@ class TestDataSource {
|
|||||||
db.setFilter("bi.test.com", [.blocked, .ignored])
|
db.setFilter("bi.test.com", [.blocked, .ignored])
|
||||||
|
|
||||||
QLog.Debug("Done")
|
QLog.Debug("Done")
|
||||||
|
}
|
||||||
Timer.repeating(2, call: #selector(insertRandom), on: self)
|
|
||||||
|
static func start() {
|
||||||
|
hook = GlassVPNHook()
|
||||||
|
timer = Timer.repeating(2, call: #selector(insertRandom), on: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func stop() {
|
||||||
|
timer?.invalidate()
|
||||||
|
timer = nil
|
||||||
|
hook.cleanUp()
|
||||||
|
hook = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc static func insertRandom() {
|
@objc static func insertRandom() {
|
||||||
//QLog.Debug("Inserting 1 periodic log entry")
|
//QLog.Debug("Inserting 1 periodic log entry")
|
||||||
try? AppDB?.logWrite("\(arc4random() % 5).count.test.com", blocked: true)
|
let rand = arc4random() % 8
|
||||||
|
let domain: String
|
||||||
|
switch rand {
|
||||||
|
case 6: domain = "tmp.b.test.com"
|
||||||
|
case 7: domain = "tmp.i.test.com"
|
||||||
|
case 8: domain = "tmp.bi.test.com"
|
||||||
|
default: domain = "\(rand).count.test.com"
|
||||||
|
}
|
||||||
|
let kill = hook.processDNSRequest(domain)
|
||||||
|
if kill { QLog.Info("Blocked: \(domain)") }
|
||||||
|
}
|
||||||
|
|
||||||
|
static func sendMsg(_ messageData: Data) {
|
||||||
|
hook.handleAppMessage(messageData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
let sync = SyncUpdate(periodic: 7)
|
||||||
|
|
||||||
class SyncUpdate {
|
class SyncUpdate {
|
||||||
private var lastSync: TimeInterval = 0
|
private var lastSync: TimeInterval = 0
|
||||||
private var timer: Timer!
|
private var timer: Timer!
|
||||||
@@ -18,8 +20,8 @@ class SyncUpdate {
|
|||||||
private(set) var tsLatest: Timestamp? // as set per user, not actual latest
|
private(set) var tsLatest: Timestamp? // as set per user, not actual latest
|
||||||
|
|
||||||
|
|
||||||
init(periodic interval: TimeInterval) {
|
fileprivate init(periodic interval: TimeInterval) {
|
||||||
(filterType, tsEarliest, tsLatest) = Pref.DateFilter.restrictions()
|
(filterType, tsEarliest, tsLatest) = Prefs.DateFilter.restrictions()
|
||||||
reloadRangeFromDB()
|
reloadRangeFromDB()
|
||||||
|
|
||||||
NotifyDateFilterChanged.observe(call: #selector(didChangeDateFilter), on: self)
|
NotifyDateFilterChanged.observe(call: #selector(didChangeDateFilter), on: self)
|
||||||
@@ -33,7 +35,7 @@ class SyncUpdate {
|
|||||||
/// Callback fired when user changes `DateFilter` on root tableView controller
|
/// Callback fired when user changes `DateFilter` on root tableView controller
|
||||||
@objc private func didChangeDateFilter() {
|
@objc private func didChangeDateFilter() {
|
||||||
self.pause()
|
self.pause()
|
||||||
let filter = Pref.DateFilter.restrictions()
|
let filter = Prefs.DateFilter.restrictions()
|
||||||
filterType = filter.type
|
filterType = filter.type
|
||||||
DispatchQueue.global().async {
|
DispatchQueue.global().async {
|
||||||
// Not necessary, but improve execution order (delete then insert).
|
// Not necessary, but improve execution order (delete then insert).
|
||||||
@@ -109,7 +111,7 @@ class SyncUpdate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if filterType == .LastXMin {
|
if filterType == .LastXMin {
|
||||||
set(newEarliest: Timestamp.past(minutes: Pref.DateFilter.LastXMin))
|
set(newEarliest: Timestamp.past(minutes: Prefs.DateFilter.LastXMin))
|
||||||
}
|
}
|
||||||
// TODO: periodic hard delete old logs (will reset rowids!)
|
// TODO: periodic hard delete old logs (will reset rowids!)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,12 +31,21 @@ func ErrorAlert(_ errorDescription: String, buttonText: String = "Dismiss") -> U
|
|||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - buttonText: Default: `"Continue"`
|
/// - buttonText: Default: `"Continue"`
|
||||||
/// - buttonStyle: Default: `.default`
|
/// - buttonStyle: Default: `.default`
|
||||||
func AskAlert(title: String?, text: String?, buttonText: String = "Continue", buttonStyle: UIAlertAction.Style = .default, action: @escaping (UIAlertController) -> Void) -> UIAlertController {
|
func AskAlert(title: String?, text: String?, buttonText: String = "Continue", cancelButton: String = "Cancel", buttonStyle: UIAlertAction.Style = .default, action: @escaping (UIAlertController) -> Void) -> UIAlertController {
|
||||||
let alert = Alert(title: title, text: text, buttonText: "Cancel")
|
let alert = Alert(title: title, text: text, buttonText: cancelButton)
|
||||||
alert.addAction(UIAlertAction(title: buttonText, style: buttonStyle) { _ in action(alert) })
|
alert.addAction(UIAlertAction(title: buttonText, style: buttonStyle) { _ in action(alert) })
|
||||||
return alert
|
return alert
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Show alert hinting the user to go to system settings and re-enable notifications.
|
||||||
|
func NotificationsDisabledAlert(presentIn viewController: UIViewController) {
|
||||||
|
AskAlert(title: "Notifications Disabled",
|
||||||
|
text: "Go to System Settings > Notifications > AppCheck to re-enable notifications.",
|
||||||
|
buttonText: "Open settings") { _ in
|
||||||
|
URL(string: UIApplication.openSettingsURLString)?.open()
|
||||||
|
}.presentIn(viewController)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Alert with multiple options
|
// MARK: Alert with multiple options
|
||||||
|
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
|
|||||||
@@ -47,6 +47,15 @@ extension NSLayoutConstraint {
|
|||||||
@discardableResult static func |(l: NSLayoutConstraint, r: UILayoutPriority) -> NSLayoutConstraint { l.priority = r; return l }
|
@discardableResult static func |(l: NSLayoutConstraint, r: UILayoutPriority) -> NSLayoutConstraint { l.priority = r; return l }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension NSLayoutDimension {
|
||||||
|
/// Create and activate an `equal` constraint with constant value. Format: `A.anchor =&= constant | priority`
|
||||||
|
@discardableResult static func =&= (l: NSLayoutDimension, r: CGFloat) -> NSLayoutConstraint { l.constraint(equalToConstant: r).on() }
|
||||||
|
/// Create and activate a `lessThan` constraint with constant value. Format: `A.anchor =<= constant | priority`
|
||||||
|
@discardableResult static func =<= (l: NSLayoutDimension, r: CGFloat) -> NSLayoutConstraint { l.constraint(lessThanOrEqualToConstant: r).on() }
|
||||||
|
/// Create and activate a `greaterThan` constraint with constant value. Format: `A.anchor =>= constant | priority`
|
||||||
|
@discardableResult static func =>= (l: NSLayoutDimension, r: CGFloat) -> NSLayoutConstraint { l.constraint(greaterThanOrEqualToConstant: r).on() }
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
UIView extension to generate multiple constraints at once
|
UIView extension to generate multiple constraints at once
|
||||||
|
|
||||||
@@ -73,6 +82,12 @@ extension UIView {
|
|||||||
return NSLayoutConstraint(item: A, attribute: $0, relatedBy: rel, toItem: B, attribute: $0, multiplier: 1, constant: margin).on()
|
return NSLayoutConstraint(item: A, attribute: $0, relatedBy: rel, toItem: B, attribute: $0, multiplier: 1, constant: margin).on()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the priority with which a view resists being made smaller and larger than its intrinsic size.
|
||||||
|
func constrainHuggingCompression(_ axis: NSLayoutConstraint.Axis, _ priotity: UILayoutPriority) {
|
||||||
|
setContentHuggingPriority(priotity, for: axis)
|
||||||
|
setContentCompressionResistancePriority(priotity, for: axis)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Array where Element: NSLayoutConstraint {
|
extension Array where Element: NSLayoutConstraint {
|
||||||
|
|||||||
25
main/Extensions/Color.swift
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import UIKit
|
||||||
|
|
||||||
|
// See: https://noahgilmore.com/blog/dark-mode-uicolor-compatibility/
|
||||||
|
extension UIColor {
|
||||||
|
/// `.systemBackground ?? .white`
|
||||||
|
static var sysBackground: UIColor { if #available(iOS 13.0, *) { return .systemBackground } else { return .white } }
|
||||||
|
/// `.link ?? .systemBlue`
|
||||||
|
static var sysLink: UIColor { if #available(iOS 13.0, *) { return .link } else { return .systemBlue } }
|
||||||
|
|
||||||
|
/// `.label ?? .black`
|
||||||
|
static var sysLabel: UIColor { if #available(iOS 13.0, *) { return .label } else { return .black } }
|
||||||
|
/// `.secondaryLabel ?? rgba(60, 60, 67, 0.6)`
|
||||||
|
static var sysLabel2: UIColor { if #available(iOS 13.0, *) { return .secondaryLabel } else { return .init(red: 60/255.0, green: 60/255.0, blue: 67/255.0, alpha: 0.6) } }
|
||||||
|
/// `.tertiaryLabel ?? rgba(60, 60, 67, 0.3)`
|
||||||
|
static var sysLabel3: UIColor { if #available(iOS 13.0, *) { return .tertiaryLabel } else { return .init(red: 60/255.0, green: 60/255.0, blue: 67/255.0, alpha: 0.3) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NSMutableAttributedString {
|
||||||
|
func withColor(_ color: UIColor, fromBack: Int) -> Self {
|
||||||
|
let l = length - fromBack
|
||||||
|
let r = (l < 0) ? NSMakeRange(0, length) : NSMakeRange(l, fromBack)
|
||||||
|
self.addAttribute(.foregroundColor, value: color, range: r)
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
}
|
||||||
33
main/Extensions/Equatable.swift
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
|
||||||
|
precedencegroup CompareAssignPrecedence {
|
||||||
|
assignment: true
|
||||||
|
associativity: left
|
||||||
|
higherThan: ComparisonPrecedence
|
||||||
|
}
|
||||||
|
|
||||||
|
infix operator <-? : CompareAssignPrecedence
|
||||||
|
infix operator <-/ : CompareAssignPrecedence
|
||||||
|
|
||||||
|
extension Equatable {
|
||||||
|
/// Assign a new value to `lhs` if `newValue` differs from the previous value. Return `false` if they are equal.
|
||||||
|
/// - Returns: `true` if `lhs` was overwritten with another value
|
||||||
|
static func <-?(lhs: inout Self, newValue: Self) -> Bool {
|
||||||
|
if lhs != newValue {
|
||||||
|
lhs = newValue
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assign a new value to `lhs` if `newValue` differs from the previous value.
|
||||||
|
/// Return tuple with both values. Or `nil` if they are equal.
|
||||||
|
/// - Returns: `nil` if `previousValue == newValue`
|
||||||
|
static func <-/(lhs: inout Self, newValue: Self) -> (previousValue: Self, newValue: Self)? {
|
||||||
|
let previousValue = lhs
|
||||||
|
if previousValue != newValue {
|
||||||
|
lhs = newValue
|
||||||
|
return (previousValue, newValue)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ extension UIFont {
|
|||||||
}
|
}
|
||||||
func bold() -> UIFont { withTraits(traits: .traitBold) }
|
func bold() -> UIFont { withTraits(traits: .traitBold) }
|
||||||
func italic() -> UIFont { withTraits(traits: .traitItalic) }
|
func italic() -> UIFont { withTraits(traits: .traitItalic) }
|
||||||
|
func boldItalic() -> UIFont { withTraits(traits: [.traitBold, .traitItalic]) }
|
||||||
func monoSpace() -> UIFont {
|
func monoSpace() -> UIFont {
|
||||||
let traits = fontDescriptor.object(forKey: .traits) as? [UIFontDescriptor.TraitKey: Any] ?? [:]
|
let traits = fontDescriptor.object(forKey: .traits) as? [UIFontDescriptor.TraitKey: Any] ?? [:]
|
||||||
let weight = (traits[.weight] as? CGFloat) ?? UIFont.Weight.regular.rawValue
|
let weight = (traits[.weight] as? CGFloat) ?? UIFont.Weight.regular.rawValue
|
||||||
@@ -13,39 +14,35 @@ extension UIFont {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NSAttributedString {
|
extension NSMutableAttributedString {
|
||||||
static func image(_ img: UIImage) -> Self {
|
convenience init(image: UIImage, centered: Bool = false) {
|
||||||
|
self.init()
|
||||||
let att = NSTextAttachment()
|
let att = NSTextAttachment()
|
||||||
att.image = img
|
att.image = image
|
||||||
return self.init(attachment: att)
|
append(.init(attachment: att))
|
||||||
|
if centered {
|
||||||
|
let ps = NSMutableParagraphStyle()
|
||||||
|
ps.alignment = .center
|
||||||
|
addAttribute(.paragraphStyle, value: ps, range: .init(location: 0, length: length))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NSMutableAttributedString {
|
extension NSMutableAttributedString {
|
||||||
static private var def: UIFont = .preferredFont(forTextStyle: .body)
|
@discardableResult func normal(_ str: String, _ style: UIFont.TextStyle = .body) -> Self { append(str, withFont: .preferredFont(forTextStyle: style)) }
|
||||||
|
@discardableResult func bold(_ str: String, _ style: UIFont.TextStyle = .body) -> Self { append(str, withFont: UIFont.preferredFont(forTextStyle: style).bold()) }
|
||||||
|
@discardableResult func italic(_ str: String, _ style: UIFont.TextStyle = .body) -> Self { append(str, withFont: UIFont.preferredFont(forTextStyle: style).italic()) }
|
||||||
|
@discardableResult func boldItalic(_ str: String, _ style: UIFont.TextStyle = .body) -> Self { append(str, withFont: UIFont.preferredFont(forTextStyle: style).boldItalic()) }
|
||||||
|
|
||||||
func normal(_ str: String, _ style: UIFont.TextStyle = .body) -> Self { append(str, withFont: .preferredFont(forTextStyle: style)) }
|
@discardableResult func h1(_ str: String) -> Self { normal(str, .title1) }
|
||||||
func bold(_ str: String, _ style: UIFont.TextStyle = .body) -> Self { append(str, withFont: UIFont.preferredFont(forTextStyle: style).bold()) }
|
@discardableResult func h2(_ str: String) -> Self { normal(str, .title2) }
|
||||||
func italic(_ str: String, _ style: UIFont.TextStyle = .body) -> Self { append(str, withFont: UIFont.preferredFont(forTextStyle: style).italic()) }
|
@discardableResult func h3(_ str: String) -> Self { normal(str, .title3) }
|
||||||
|
|
||||||
func h1(_ str: String) -> Self { normal(str, .title1) }
|
|
||||||
func h2(_ str: String) -> Self { normal(str, .title2) }
|
|
||||||
func h3(_ str: String) -> Self { normal(str, .title3) }
|
|
||||||
|
|
||||||
private func append(_ str: String, withFont: UIFont) -> Self {
|
private func append(_ str: String, withFont: UIFont) -> Self {
|
||||||
append(NSAttributedString(string: str, attributes: [
|
append(NSAttributedString(string: str, attributes: [
|
||||||
.font : withFont,
|
.font : withFont,
|
||||||
.foregroundColor : UIColor.sysFg
|
.foregroundColor : UIColor.sysLabel
|
||||||
]))
|
]))
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
func centered(_ content: NSAttributedString) -> Self {
|
|
||||||
let before = length
|
|
||||||
append(content)
|
|
||||||
let ps = NSMutableParagraphStyle()
|
|
||||||
ps.alignment = .center
|
|
||||||
addAttribute(.paragraphStyle, value: ps, range: .init(location: before, length: content.length))
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
import UIKit
|
|
||||||
|
|
||||||
struct QLog {
|
|
||||||
private init() {}
|
|
||||||
static func m(_ message: String) { write("", message) }
|
|
||||||
static func Info(_ message: String) { write("[INFO] ", message) }
|
|
||||||
#if DEBUG
|
|
||||||
static func Debug(_ message: String) { write("[DEBUG] ", message) }
|
|
||||||
#else
|
|
||||||
static func Debug(_ _: String) {}
|
|
||||||
#endif
|
|
||||||
static func Error(_ message: String) { write("[ERROR] ", message) }
|
|
||||||
static func Warning(_ message: String) { write("[WARN] ", message) }
|
|
||||||
private static func write(_ tag: String, _ message: String) {
|
|
||||||
print(String(format: "%1.3f %@%@", Date().timeIntervalSince1970, tag, message))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension UIColor {
|
|
||||||
static var sysBg: UIColor { get { if #available(iOS 13.0, *) { return .systemBackground } else { return .white } }}
|
|
||||||
static var sysFg: UIColor { get { if #available(iOS 13.0, *) { return .label } else { return .black } }}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension UIEdgeInsets {
|
|
||||||
init(all: CGFloat = 0, top: CGFloat? = nil, left: CGFloat? = nil, bottom: CGFloat? = nil, right: CGFloat? = nil) {
|
|
||||||
self.init(top: top ?? all, left: left ?? all, bottom: bottom ?? all, right: right ?? all)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
precedencegroup CompareAssignPrecedence {
|
|
||||||
assignment: true
|
|
||||||
associativity: left
|
|
||||||
higherThan: ComparisonPrecedence
|
|
||||||
}
|
|
||||||
|
|
||||||
infix operator <-? : CompareAssignPrecedence
|
|
||||||
infix operator <-/ : CompareAssignPrecedence
|
|
||||||
extension Equatable {
|
|
||||||
/// Assign a new value to `lhs` if `newValue` differs from the previous value. Return `false` if they are equal.
|
|
||||||
/// - Returns: `true` if `lhs` was overwritten with another value
|
|
||||||
static func <-?(lhs: inout Self, newValue: Self) -> Bool {
|
|
||||||
if lhs != newValue {
|
|
||||||
lhs = newValue
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Assign a new value to `lhs` if `newValue` differs from the previous value.
|
|
||||||
/// Return tuple with both values. Or `nil` if they are equal.
|
|
||||||
/// - Returns: `nil` if `previousValue == newValue`
|
|
||||||
static func <-/(lhs: inout Self, newValue: Self) -> (previousValue: Self, newValue: Self)? {
|
|
||||||
let previousValue = lhs
|
|
||||||
if previousValue != newValue {
|
|
||||||
lhs = newValue
|
|
||||||
return (previousValue, newValue)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
17
main/Extensions/Logging.swift
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct QLog {
|
||||||
|
private init() {}
|
||||||
|
static func m(_ message: String) { write("", message) }
|
||||||
|
static func Info(_ message: String) { write("[INFO] ", message) }
|
||||||
|
#if DEBUG
|
||||||
|
static func Debug(_ message: String) { write("[DEBUG] ", message) }
|
||||||
|
#else
|
||||||
|
static func Debug(_ _: String) {}
|
||||||
|
#endif
|
||||||
|
static func Error(_ message: String) { write("[ERROR] ", message) }
|
||||||
|
static func Warning(_ message: String) { write("[WARN] ", message) }
|
||||||
|
private static func write(_ tag: String, _ message: String) {
|
||||||
|
print(String(format: "%1.3f %@%@", Date().timeIntervalSince1970, tag, message))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
let NotifyVPNStateChanged = NSNotification.Name("GlassVPNStateChanged") // VPNState!
|
let NotifyVPNStateChanged = NSNotification.Name("GlassVPNStateChanged") // nil!
|
||||||
let NotifyDNSFilterChanged = NSNotification.Name("PSIDNSFilterSettingsChanged") // domain: String!
|
let NotifyDNSFilterChanged = NSNotification.Name("PSIDNSFilterSettingsChanged") // domain: String!
|
||||||
let NotifyDateFilterChanged = NSNotification.Name("PSIDateFilterSettingsChanged") // nil!
|
let NotifyDateFilterChanged = NSNotification.Name("PSIDateFilterSettingsChanged") // nil!
|
||||||
let NotifySortOrderChanged = NSNotification.Name("PSIDateFilterSortOrderChanged") // nil!
|
let NotifySortOrderChanged = NSNotification.Name("PSIDateFilterSortOrderChanged") // nil!
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
var currentVPNState: VPNState = .off
|
|
||||||
let sync = SyncUpdate(periodic: 7)
|
|
||||||
|
|
||||||
public enum VPNState : Int {
|
|
||||||
case on = 1, inbetween, off
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Pref {
|
|
||||||
static func Int(_ key: String) -> Int { UserDefaults.standard.integer(forKey: key) }
|
|
||||||
static func Int(_ val: Int, _ key: String) { UserDefaults.standard.set(val, forKey: key) }
|
|
||||||
static func Bool(_ key: String) -> Bool { UserDefaults.standard.bool(forKey: key) }
|
|
||||||
static func Bool(_ val: Bool, _ key: String) { UserDefaults.standard.set(val, forKey: key) }
|
|
||||||
static func `Any`(_ key: String) -> Any? { UserDefaults.standard.object(forKey: key) }
|
|
||||||
static func `Any`(_ val: Any?, _ key: String) { UserDefaults.standard.set(val, forKey: key) }
|
|
||||||
|
|
||||||
enum DidShowTutorial {
|
|
||||||
static var Welcome: Bool {
|
|
||||||
get { Pref.Bool("didShowTutorialAppWelcome") }
|
|
||||||
set { Pref.Bool(newValue, "didShowTutorialAppWelcome") }
|
|
||||||
}
|
|
||||||
static var Recordings: Bool {
|
|
||||||
get { Pref.Bool("didShowTutorialRecordings") }
|
|
||||||
set { Pref.Bool(newValue, "didShowTutorialRecordings") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
enum ContextAnalyis {
|
|
||||||
static var CoOccurrenceTime: Int? {
|
|
||||||
get { Pref.Any("contextAnalyisCoOccurrenceTime") as? Int }
|
|
||||||
set { Pref.Any(newValue, "contextAnalyisCoOccurrenceTime") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
enum DateFilter {
|
|
||||||
static var Kind: DateFilterKind {
|
|
||||||
get { DateFilterKind(rawValue: Pref.Int("dateFilterType"))! }
|
|
||||||
set { Pref.Int(newValue.rawValue, "dateFilterType") }
|
|
||||||
}
|
|
||||||
/// Default: `0` (disabled)
|
|
||||||
static var LastXMin: Int {
|
|
||||||
get { Pref.Int("dateFilterLastXMin") }
|
|
||||||
set { Pref.Int(newValue, "dateFilterLastXMin") }
|
|
||||||
}
|
|
||||||
/// Default: `nil` (disabled)
|
|
||||||
static var RangeA: Timestamp? {
|
|
||||||
get { Pref.Any("dateFilterRangeA") as? Timestamp }
|
|
||||||
set { Pref.Any(newValue, "dateFilterRangeA") }
|
|
||||||
}
|
|
||||||
/// Default: `nil` (disabled)
|
|
||||||
static var RangeB: Timestamp? {
|
|
||||||
get { Pref.Any("dateFilterRangeB") as? Timestamp }
|
|
||||||
set { Pref.Any(newValue, "dateFilterRangeB") }
|
|
||||||
}
|
|
||||||
/// default: `.Date`
|
|
||||||
static var OrderBy: DateFilterOrderBy {
|
|
||||||
get { DateFilterOrderBy(rawValue: Pref.Int("dateFilterOderType"))! }
|
|
||||||
set { Pref.Int(newValue.rawValue, "dateFilterOderType") }
|
|
||||||
}
|
|
||||||
/// default: `false` (Desc)
|
|
||||||
static var OrderAsc: Bool {
|
|
||||||
get { Pref.Bool("dateFilterOderAsc") }
|
|
||||||
set { Pref.Bool(newValue, "dateFilterOderAsc") }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// - Returns: Timestamp restriction depending on current selected date filter.
|
|
||||||
/// - `Off` : `(nil, nil)`
|
|
||||||
/// - `LastXMin` : `(now-LastXMin, nil)`
|
|
||||||
/// - `ABRange` : `(RangeA, RangeB)`
|
|
||||||
static func restrictions() -> (type: DateFilterKind, earliest: Timestamp?, latest: Timestamp?) {
|
|
||||||
let type = Kind
|
|
||||||
switch type {
|
|
||||||
case .Off: return (type, nil, nil)
|
|
||||||
case .LastXMin: return (type, Timestamp.past(minutes: Pref.DateFilter.LastXMin), nil)
|
|
||||||
case .ABRange: return (type, Pref.DateFilter.RangeA, Pref.DateFilter.RangeB)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
enum DateFilterKind: Int {
|
|
||||||
case Off = 0, LastXMin = 1, ABRange = 2;
|
|
||||||
}
|
|
||||||
enum DateFilterOrderBy: Int {
|
|
||||||
case Date = 0, Name = 1, Count = 2;
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,5 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
extension NSMutableAttributedString {
|
|
||||||
func withColor(_ color: UIColor, fromBack: Int) -> Self {
|
|
||||||
let l = length - fromBack
|
|
||||||
let r = (l < 0) ? NSMakeRange(0, length) : NSMakeRange(l, fromBack)
|
|
||||||
self.addAttribute(.foregroundColor, value: color, range: r)
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension String {
|
extension String {
|
||||||
/// Check if string is equal to `domain` or ends with `.domain`
|
/// Check if string is equal to `domain` or ends with `.domain`
|
||||||
func isSubdomain(of domain: String) -> Bool { self == domain || self.hasSuffix("." + domain) }
|
func isSubdomain(of domain: String) -> Bool { self == domain || self.hasSuffix("." + domain) }
|
||||||
@@ -34,6 +25,13 @@ extension String {
|
|||||||
let parts = components(separatedBy: ".")
|
let parts = components(separatedBy: ".")
|
||||||
return parts.count == 2 && listOfSLDs[parts.last!]?[parts.first!] ?? false
|
return parts.count == 2 && listOfSLDs[parts.last!]?[parts.first!] ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isValidBundleId() -> Bool {
|
||||||
|
let regex = try! NSRegularExpression(pattern: #"^[A-Za-z0-9\.\-]{1,155}$"#, options: .anchorsMatchLines)
|
||||||
|
let range = NSRange(location: 0, length: self.utf16.count)
|
||||||
|
let matches = regex.matches(in: self, options: .anchored, range: range)
|
||||||
|
return matches.count == 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var listOfSLDs: [String : [String : Bool]] = {
|
private var listOfSLDs: [String : [String : Bool]] = {
|
||||||
@@ -49,3 +47,9 @@ private var listOfSLDs: [String : [String : Bool]] = {
|
|||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
extension NSString {
|
||||||
|
func substring(from: Int, to: Int) -> String {
|
||||||
|
substring(with: NSRange(location: from, length: to - from))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,14 +43,6 @@ extension UITableView {
|
|||||||
func safeMoveRow(_ from: Int, to: Int) {
|
func safeMoveRow(_ from: Int, to: Int) {
|
||||||
isFrontmost ? moveRow(at: IndexPath(row: from), to: IndexPath(row: to)) : reloadData()
|
isFrontmost ? moveRow(at: IndexPath(row: from), to: IndexPath(row: to)) : reloadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Recalculate and apply new `tableHeaderView` height.
|
|
||||||
func sizeHeaderToFit() {
|
|
||||||
if let head = tableHeaderView {
|
|
||||||
head.frame.size.height = head.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
|
|
||||||
tableHeaderView = head
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -98,3 +90,34 @@ extension EditActionsRemove where Self: UITableViewController {
|
|||||||
func editableRowActions(_: IndexPath) -> [(RowAction, String)] { [(.delete, "Remove")] }
|
func editableRowActions(_: IndexPath) -> [(RowAction, String)] { [(.delete, "Remove")] }
|
||||||
func editableRowActionColor(_: IndexPath, _: RowAction) -> UIColor? { nil }
|
func editableRowActionColor(_: IndexPath, _: RowAction) -> UIColor? { nil }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Table Cell Tap Menu
|
||||||
|
|
||||||
|
struct TableCellTapMenu {
|
||||||
|
private var index: Int = Int.max
|
||||||
|
|
||||||
|
mutating func reset() { index = Int.max }
|
||||||
|
|
||||||
|
/// Create a new tap manu and shows it immediatelly. With optional buttons.
|
||||||
|
mutating func start(_ tableView: UITableView, _ indexPath: IndexPath, items: [UIMenuItem]? = nil) -> Bool {
|
||||||
|
let menu = UIMenuController.shared
|
||||||
|
if index == indexPath.row {
|
||||||
|
menu.setMenuVisible(false, animated: true)
|
||||||
|
reset()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
index = indexPath.row
|
||||||
|
let cell = tableView.cellForRow(at: indexPath)!
|
||||||
|
menu.setTargetRect(cell.bounds, in: cell)
|
||||||
|
menu.menuItems = items
|
||||||
|
menu.setMenuVisible(true, animated: true)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the item if the array index is in bounds.
|
||||||
|
func getSelected<T>(_ source: [T]) -> T? {
|
||||||
|
guard index < source.count else { return nil }
|
||||||
|
return source[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||