Merge pull request #14 from keeshux/itunes-file-sharing
Enable iTunes File Sharing
This commit is contained in:
commit
8961bfe03e
|
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Added
|
||||
|
||||
- Attach .ovpn when reporting a connectivity issue, stripped of sensitive data. [#13](https://github.com/keeshux/passepartout-ios/pull/13)
|
||||
- iTunes File Sharing (skythedesu). [#14](https://github.com/keeshux/passepartout-ios/pull/14)
|
||||
|
||||
## 1.0 beta 1107 (2018-10-26)
|
||||
|
||||
|
|
|
@ -90,6 +90,7 @@ internal enum StoryboardSegue {
|
|||
case addProviderSegueIdentifier = "AddProviderSegueIdentifier"
|
||||
case creditsSegueIdentifier = "CreditsSegueIdentifier"
|
||||
case disclaimerSegueIdentifier = "DisclaimerSegueIdentifier"
|
||||
case importHostSegueIdentifier = "ImportHostSegueIdentifier"
|
||||
case selectProfileSegueIdentifier = "SelectProfileSegueIdentifier"
|
||||
case versionSegueIdentifier = "VersionSegueIdentifier"
|
||||
}
|
||||
|
|
|
@ -43,6 +43,8 @@
|
|||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<false/>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
//
|
||||
// ImportedHostsViewController.swift
|
||||
// Passepartout-iOS
|
||||
//
|
||||
// Created by Davide De Rosa on 10/27/18.
|
||||
// Copyright (c) 2018 Davide De Rosa. All rights reserved.
|
||||
//
|
||||
// https://github.com/keeshux
|
||||
//
|
||||
// This file is part of Passepartout.
|
||||
//
|
||||
// Passepartout is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Passepartout is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import TunnelKit
|
||||
import SwiftyBeaver
|
||||
|
||||
private let log = SwiftyBeaver.self
|
||||
|
||||
class ImportedHostsViewController: UITableViewController {
|
||||
private lazy var pendingConfigurationURLs = TransientStore.shared.service.pendingConfigurationURLs().sorted { $0.normalizedFilename < $1.normalizedFilename }
|
||||
|
||||
private var parsedFile: ParsedFile?
|
||||
|
||||
weak var wizardDelegate: WizardDelegate?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
title = L10n.ImportedHosts.title
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
guard !pendingConfigurationURLs.isEmpty else {
|
||||
let alert = Macros.alert(
|
||||
L10n.ImportedHosts.title,
|
||||
L10n.Organizer.Alerts.AddHost.message
|
||||
)
|
||||
alert.addCancelAction(L10n.Global.ok) {
|
||||
self.close()
|
||||
}
|
||||
present(alert, animated: true, completion: nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Actions
|
||||
|
||||
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
||||
guard let cell = sender as? UITableViewCell, let indexPath = tableView.indexPath(for: cell) else {
|
||||
return false
|
||||
}
|
||||
let url = pendingConfigurationURLs[indexPath.row]
|
||||
guard let parsedFile = ParsedFile.from(url, withErrorAlertIn: self) else {
|
||||
if let selectedIP = tableView.indexPathForSelectedRow {
|
||||
tableView.deselectRow(at: selectedIP, animated: true)
|
||||
}
|
||||
return false
|
||||
}
|
||||
self.parsedFile = parsedFile
|
||||
return true
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
guard let wizard = segue.destination as? WizardHostViewController else {
|
||||
return
|
||||
}
|
||||
wizard.parsedFile = parsedFile
|
||||
wizard.delegate = wizardDelegate
|
||||
}
|
||||
|
||||
@IBAction private func close() {
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension ImportedHostsViewController {
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return pendingConfigurationURLs.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let url = pendingConfigurationURLs[indexPath.row]
|
||||
let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
|
||||
cell.leftText = url.normalizedFilename
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
|
||||
let url = pendingConfigurationURLs[indexPath.row]
|
||||
try? FileManager.default.removeItem(at: url)
|
||||
pendingConfigurationURLs.remove(at: indexPath.row)
|
||||
tableView.deleteRows(at: [indexPath], with: .top)
|
||||
}
|
||||
}
|
|
@ -139,6 +139,8 @@ class OrganizerViewController: UITableViewController, TableModelHost {
|
|||
providerVC.availableNames = availableProviderNames ?? []
|
||||
}
|
||||
vc.delegate = self
|
||||
} else if let vc = destination as? ImportedHostsViewController {
|
||||
vc.wizardDelegate = self
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,12 +176,7 @@ class OrganizerViewController: UITableViewController, TableModelHost {
|
|||
}
|
||||
|
||||
private func addNewHost() {
|
||||
let alert = Macros.alert(
|
||||
L10n.Organizer.Sections.Hosts.header,
|
||||
L10n.Organizer.Alerts.AddHost.message
|
||||
)
|
||||
alert.addCancelAction(L10n.Global.ok)
|
||||
present(alert, animated: true, completion: nil)
|
||||
perform(segue: StoryboardSegue.Organizer.importHostSegueIdentifier)
|
||||
}
|
||||
|
||||
private func removeProfile(at indexPath: IndexPath) {
|
||||
|
|
|
@ -161,6 +161,76 @@
|
|||
</objects>
|
||||
<point key="canvasLocation" x="-2815" y="-279"/>
|
||||
</scene>
|
||||
<!--Imported Hosts View Controller-->
|
||||
<scene sceneID="0zM-zA-ZTU">
|
||||
<objects>
|
||||
<tableViewController id="c0p-pg-Arz" customClass="ImportedHostsViewController" customModule="Passepartout_iOS" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="j8e-Ab-2SM">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="SettingTableViewCell" textLabel="h2k-uQ-JBT" detailTextLabel="Tnk-4u-fB2" style="IBUITableViewCellStyleValue1" id="4dG-dn-eeq" customClass="SettingTableViewCell" customModule="Passepartout_iOS" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="28" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="4dG-dn-eeq" id="c48-Fg-ve4">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="h2k-uQ-JBT">
|
||||
<rect key="frame" x="16" y="12" width="33.5" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Detail" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Tnk-4u-fB2">
|
||||
<rect key="frame" x="315" y="12" width="44" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="oga-go-FqD" kind="show" id="kwN-PZ-Zg5"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="c0p-pg-Arz" id="Ryi-oH-bPG"/>
|
||||
<outlet property="delegate" destination="c0p-pg-Arz" id="Ct1-QV-YrO"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" id="TpS-QE-QmN">
|
||||
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="R3x-hK-EC4">
|
||||
<connections>
|
||||
<action selector="close" destination="c0p-pg-Arz" id="yVS-jW-gwZ"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="c40-c8-YA4" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-2502" y="171"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="pgr-Kw-Y4E">
|
||||
<objects>
|
||||
<navigationController id="z6E-m6-Op0" sceneMemberID="viewController">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="NoF-OX-w8Z">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<connections>
|
||||
<segue destination="c0p-pg-Arz" kind="relationship" relationship="rootViewController" id="tms-L9-AIy"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="oY5-42-xYk" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-3270" y="171"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="ajI-cA-lhi">
|
||||
<objects>
|
||||
|
@ -175,7 +245,7 @@
|
|||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="BvG-Pe-O1S" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-1786" y="-356"/>
|
||||
<point key="canvasLocation" x="-1727" y="-357"/>
|
||||
</scene>
|
||||
<!--Organizer View Controller-->
|
||||
<scene sceneID="H6i-Tl-Gnq">
|
||||
|
@ -223,6 +293,7 @@
|
|||
<connections>
|
||||
<segue destination="NVA-bQ-iIE" kind="presentation" identifier="AddProviderSegueIdentifier" modalPresentationStyle="formSheet" id="Win-5U-mIc"/>
|
||||
<segue destination="a3d-vD-Pr7" kind="presentation" identifier="AboutSegueIdentifier" id="fd4-we-46n"/>
|
||||
<segue destination="z6E-m6-Op0" kind="presentation" identifier="ImportHostSegueIdentifier" modalPresentationStyle="formSheet" id="TZv-OK-8vU"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="bGp-H5-24W" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
|
@ -496,5 +567,6 @@
|
|||
</resources>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="HW6-RJ-VFY"/>
|
||||
<segue reference="qQl-B5-moM"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
</document>
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
0EA068F4218475F800C320AD /* ParsedFile+Alerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA068F3218475F800C320AD /* ParsedFile+Alerts.swift */; };
|
||||
0EAAD71920E6669A0088754A /* GroupConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE8DED20C93E4C004C739C /* GroupConstants.swift */; };
|
||||
0EB60FDA2111136E00AD27F3 /* UITextView+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB60FD92111136E00AD27F3 /* UITextView+Search.swift */; };
|
||||
0EB67D6B2184581E00BA6200 /* ImportedHostsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB67D6A2184581E00BA6200 /* ImportedHostsViewController.swift */; };
|
||||
0EBBE8F221822B4D00106008 /* ConnectionServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBBE8F021822B4D00106008 /* ConnectionServiceTests.swift */; };
|
||||
0EBBE8F321822B4D00106008 /* ConnectionService.json in Resources */ = {isa = PBXBuildFile; fileRef = 0EBBE8F121822B4D00106008 /* ConnectionService.json */; };
|
||||
0EBBE8F52182361800106008 /* ConnectionService+Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBBE8F42182361700106008 /* ConnectionService+Migration.swift */; };
|
||||
|
@ -169,6 +170,7 @@
|
|||
0E8D97E421389276006FB4A0 /* pia.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = pia.json; sourceTree = "<group>"; };
|
||||
0EA068F3218475F800C320AD /* ParsedFile+Alerts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParsedFile+Alerts.swift"; sourceTree = "<group>"; };
|
||||
0EB60FD92111136E00AD27F3 /* UITextView+Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextView+Search.swift"; sourceTree = "<group>"; };
|
||||
0EB67D6A2184581E00BA6200 /* ImportedHostsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportedHostsViewController.swift; sourceTree = "<group>"; };
|
||||
0EBBE8F021822B4D00106008 /* ConnectionServiceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionServiceTests.swift; sourceTree = "<group>"; };
|
||||
0EBBE8F121822B4D00106008 /* ConnectionService.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ConnectionService.json; sourceTree = "<group>"; };
|
||||
0EBBE8F42182361700106008 /* ConnectionService+Migration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConnectionService+Migration.swift"; sourceTree = "<group>"; };
|
||||
|
@ -344,6 +346,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
0EE3BBB1215ED3A900F30952 /* AboutViewController.swift */,
|
||||
0EB67D6A2184581E00BA6200 /* ImportedHostsViewController.swift */,
|
||||
0EFD943F215BED8E00529B64 /* LabelViewController.swift */,
|
||||
0EBE3A78213C4E5400BFA2F5 /* OrganizerViewController.swift */,
|
||||
0E3DA370215CB5BF00B40FC9 /* VersionViewController.swift */,
|
||||
|
@ -852,6 +855,7 @@
|
|||
0ED38AEA214054A50004D387 /* OptionViewController.swift in Sources */,
|
||||
0EFD943E215BE10800529B64 /* IssueReporter.swift in Sources */,
|
||||
0EB60FDA2111136E00AD27F3 /* UITextView+Search.swift in Sources */,
|
||||
0EB67D6B2184581E00BA6200 /* ImportedHostsViewController.swift in Sources */,
|
||||
0E57F63E20C83FC5008323CF /* ServiceViewController.swift in Sources */,
|
||||
0E39BCF0214B9EF10035E9DE /* WebServices.swift in Sources */,
|
||||
0EDE8DE720C93945004C739C /* Credentials.swift in Sources */,
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
"organizer.cells.about.caption" = "About %@";
|
||||
"organizer.cells.uninstall.caption" = "Delete VPN profile";
|
||||
"organizer.alerts.exhausted_providers.message" = "You have created profiles for any available network.";
|
||||
"organizer.alerts.add_host.message" = "Open an URL to an .ovpn configuration file from Safari, Mail or another app to set up a host profile.";
|
||||
"organizer.alerts.add_host.message" = "Open an URL to an .ovpn configuration file from Safari, Mail or another app to set up a host profile.\n\nYou can also import an .ovpn with iTunes File Sharing.";
|
||||
"organizer.alerts.delete_vpn_profile.message" = "Do you really want to delete the VPN profile from the device?";
|
||||
|
||||
"account.suggestion_footer.infrastructure.pia" = "Use your website credentials. Your username is usually numeric with a \"p\" prefix.";
|
||||
|
@ -58,6 +58,8 @@
|
|||
"parsed_file.alerts.parsing.message" = "Unable to parse the provided configuration file (%@).";
|
||||
"parsed_file.alerts.buttons.report" = "Report an issue";
|
||||
|
||||
"imported_hosts.title" = "Imported";
|
||||
|
||||
"service.welcome.message" = "Welcome to Passepartout!\n\nUse the organizer to add a new profile.";
|
||||
"service.sections.general.header" = "General";
|
||||
"service.sections.vpn.header" = "VPN";
|
||||
|
|
|
@ -24,6 +24,9 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftyBeaver
|
||||
|
||||
private let log = SwiftyBeaver.self
|
||||
|
||||
extension ConnectionService {
|
||||
func save(configurationURL: URL, for profile: ConnectionProfile) throws -> URL {
|
||||
|
@ -46,4 +49,14 @@ extension ConnectionService {
|
|||
let contextURL = ConnectionService.ProfileKey(profile).contextURL(in: self)
|
||||
return contextURL.appendingPathComponent(profile.id).appendingPathExtension("ovpn")
|
||||
}
|
||||
|
||||
func pendingConfigurationURLs() -> [URL] {
|
||||
do {
|
||||
let list = try FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: [])
|
||||
return list.filter { $0.pathExtension == "ovpn" }
|
||||
} catch let e {
|
||||
log.error("Could not list imported configurations: \(e)")
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -300,6 +300,11 @@ internal enum L10n {
|
|||
internal static let ok = L10n.tr("Localizable", "global.ok")
|
||||
}
|
||||
|
||||
internal enum ImportedHosts {
|
||||
/// Imported
|
||||
internal static let title = L10n.tr("Localizable", "imported_hosts.title")
|
||||
}
|
||||
|
||||
internal enum IssueReporter {
|
||||
/// The debug log of your latest connections is crucial to resolve your connectivity issues and is completely anonymous.\n\nThe .ovpn configuration file, if any, is attached stripped of any sensitive data.\n\nPlease double check the email attachments if unsure.
|
||||
internal static let message = L10n.tr("Localizable", "issue_reporter.message")
|
||||
|
@ -338,7 +343,7 @@ internal enum L10n {
|
|||
internal enum Alerts {
|
||||
|
||||
internal enum AddHost {
|
||||
/// Open an URL to an .ovpn configuration file from Safari, Mail or another app to set up a host profile.
|
||||
/// Open an URL to an .ovpn configuration file from Safari, Mail or another app to set up a host profile.\n\nYou can also import an .ovpn with iTunes File Sharing.
|
||||
internal static let message = L10n.tr("Localizable", "organizer.alerts.add_host.message")
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue