Skip to content

Commit

Permalink
fix download of urls containing a percent (%) character
Browse files Browse the repository at this point in the history
  • Loading branch information
yep committed May 4, 2018
1 parent 4f1db61 commit 315d1a6
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 82 deletions.
19 changes: 18 additions & 1 deletion App Downloader.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,12 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0820;
LastUpgradeCheck = 0820;
LastUpgradeCheck = 0930;
TargetAttributes = {
0D45036F1E2431CC00F5F4FE = {
CreatedOnToolsVersion = 8.2.1;
DevelopmentTeam = 2AD47BTDQ6;
LastSwiftMigration = 0930;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.Sandbox = {
Expand Down Expand Up @@ -181,15 +182,23 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
Expand Down Expand Up @@ -230,15 +239,23 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
Expand Down
4 changes: 1 addition & 3 deletions App Downloader/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// AppDelegate.swift
// AppDownloader
//
// Copyright (C) 2017 Jahn Bertsch
// Copyright (C) 2017, 2018 Jahn Bertsch
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
Expand All @@ -23,14 +23,12 @@ import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}

func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}

}

40 changes: 19 additions & 21 deletions App Downloader/Download.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Download.swift
// AppDownloader
//
// Copyright (C) 2017 Jahn Bertsch
// Copyright (C) 2017, 2018 Jahn Bertsch
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
Expand All @@ -22,18 +22,17 @@
import Foundation
import AppKit

protocol DownloadLocationDelegate {
protocol DownloadLocationDelegate: class {
func downloadLocationError(messageText: String, informativeText: String)
func downloadLocationFound(url: URL)
}

class Download: NSObject, URLSessionDelegate, DownloadLocationProtocol {

private let config = URLSessionConfiguration.default
private var session: URLSession?
private var downloadLocationTask: URLSessionDownloadTask?

public var delegate: DownloadLocationDelegate? = nil
public weak var delegate: DownloadLocationDelegate? = nil

override init() {
super.init()
Expand All @@ -47,7 +46,7 @@ class Download: NSObject, URLSessionDelegate, DownloadLocationProtocol {

// MARK: - get download location

func getDownloadLocationCompletionHandler(dataOptional: Data?, responseOptional: URLResponse?, errorOptional: Error?) {
fileprivate func getDownloadLocationCompletionHandler(dataOptional: Data?, responseOptional: URLResponse?, errorOptional: Error?) {
if let error = errorOptional {
delegate?.downloadLocationError(messageText: "Download Location Unknown", informativeText: error.localizedDescription)
} else {
Expand All @@ -57,17 +56,16 @@ class Download: NSObject, URLSessionDelegate, DownloadLocationProtocol {
}
}

func parseDownloadLocation(data: Data) {
fileprivate func parseDownloadLocation(data: Data) {
do {
let json = try JSONSerialization.jsonObject(with: data) as! [String: AnyObject]
parseDownloadLocation(json: json)
} catch {
delegate?.downloadLocationError(messageText: "Error", informativeText: "JSON decoding failed: \(error.localizedDescription)")
}
}


func parseDownloadLocation(json: [String: AnyObject]) {
fileprivate func parseDownloadLocation(json: [String: AnyObject]) {
if let downloadUrl = json["download_url"] as? String {
if let url = URL(string: downloadUrl) {
let task = session?.dataTask(with: url, completionHandler: getCaskFileCompletionHandler)
Expand All @@ -78,7 +76,7 @@ class Download: NSObject, URLSessionDelegate, DownloadLocationProtocol {

// MARK: - get cask file

func getCaskFileCompletionHandler(dataOptional: Data?, responseOptional: URLResponse?, errorOptional: Error?) {
fileprivate func getCaskFileCompletionHandler(dataOptional: Data?, responseOptional: URLResponse?, errorOptional: Error?) {
if let error = errorOptional {
delegate?.downloadLocationError(messageText: "Download failed", informativeText: error.localizedDescription)
} else {
Expand All @@ -90,7 +88,7 @@ class Download: NSObject, URLSessionDelegate, DownloadLocationProtocol {
}
}

func parse(caskFile: String) {
fileprivate func parse(caskFile: String) {
let (version, _) = parseCaskFile(caskFile: caskFile)

if var downloadUrl = extract(searchString: "url", from: caskFile) {
Expand All @@ -110,7 +108,7 @@ class Download: NSObject, URLSessionDelegate, DownloadLocationProtocol {

// MARK: - private

private func parseCaskFile(caskFile: String) -> (String, String) {
fileprivate func parseCaskFile(caskFile: String) -> (String, String) {
var version = "", sha256 = ""
let caskFileLines = caskFile.components(separatedBy: .newlines)

Expand All @@ -128,7 +126,7 @@ class Download: NSObject, URLSessionDelegate, DownloadLocationProtocol {
return (version, sha256)
}

private func replace(version: String, in source: String) -> String {
fileprivate func replace(version: String, in source: String) -> String {
let (major, minor, patch, patchOnly, beforeComma, afterComma, afterCommaBeforeColon, afterColon) = split(version: version)

var result = source.replacingOccurrences(of: "#{version}", with: version)
Expand All @@ -155,7 +153,7 @@ class Download: NSObject, URLSessionDelegate, DownloadLocationProtocol {
private func split(version: String) -> (String, String, String, String, String, String, String, String) {
var versionMajor = "", versionMinor = "", versionPatch = "", versionPatchOnly = "", beforeComma = "", afterComma = "", afterCommaBeforeColon = "", afterColon = ""

let versionArray = version.characters.split(separator: ".")
let versionArray = version.split(separator: ".")

if versionArray.count >= 3 {
versionPatch = String(versionArray[2])
Expand All @@ -167,34 +165,34 @@ class Download: NSObject, URLSessionDelegate, DownloadLocationProtocol {
versionMajor = String(versionArray[0])
}

let patchArray = versionPatch.characters.split(separator: "-")
let patchArray = versionPatch.split(separator: "-")
if patchArray.count > 0 {
versionPatchOnly = String(patchArray[0])
}

let commaArray = version.characters.split(separator: ",")
let commaArray = version.split(separator: ",")
if commaArray.count > 1 {
beforeComma = String(commaArray[0])
afterComma = String(commaArray[1])

let beforeColonArray = afterComma.characters.split(separator: ":")
let beforeColonArray = afterComma.split(separator: ":")
if beforeColonArray.count > 1 {
afterCommaBeforeColon = String(beforeColonArray[0])
}
}

let colonArray = version.characters.split(separator: ":")
let colonArray = version.split(separator: ":")
if colonArray.count > 1 {
afterColon = String(colonArray[1])
}

return (versionMajor, versionMinor, versionPatch, versionPatchOnly, beforeComma, afterComma, afterCommaBeforeColon, afterColon)
}

private func extract(searchString: String, from source: String) -> String? {
fileprivate func extract(searchString: String, from source: String) -> String? {
if let sourceLine = extractTextLine(containingString: searchString, in: source) {
let sourceLineWithoutSpaces = sourceLine.replacingOccurrences(of: ", '", with: ",'")
var sourceArray = sourceLineWithoutSpaces.characters.split(separator: " ").map(String.init)
var sourceArray = sourceLineWithoutSpaces.split(separator: " ").map(String.init)
if sourceArray.count == 2 {
return trim(sourceArray[1])
}
Expand All @@ -203,11 +201,11 @@ class Download: NSObject, URLSessionDelegate, DownloadLocationProtocol {
return nil
}

private func trim(_ source: String) -> String {
fileprivate func trim(_ source: String) -> String {
return source.trimmingCharacters(in: CharacterSet(charactersIn: ",\"'\n"))
}

private func extractTextLine(containingString searchString: String, in sourceString: String) -> String? {
fileprivate func extractTextLine(containingString searchString: String, in sourceString: String) -> String? {
if let searchRange = sourceString.range(of: searchString) {
return sourceString.substring(with: sourceString.lineRange(for: searchRange))
}
Expand Down
4 changes: 2 additions & 2 deletions App Downloader/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>1.0.1</string>
<key>CFBundleVersion</key>
<string>2</string>
<string>3</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
Expand Down
61 changes: 34 additions & 27 deletions App Downloader/Search.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Search.swift
// AppDownloader
//
// Copyright (C) 2017 Jahn Bertsch
// Copyright (C) 2017, 2018 Jahn Bertsch
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
Expand All @@ -21,18 +21,17 @@

import Foundation

protocol SearchDelegate {
protocol SearchDelegate: class {
func searchError(messageText: String, informativeText: String)
func resetSearchResults(statusText: String)
func display(searchResults: [SearchResult])
}

class Search: SearchProtocol {

private let config: URLSessionConfiguration
private let session: URLSession

public var delegate: SearchDelegate? = nil
public weak var delegate: SearchDelegate? = nil

init() {
config = URLSessionConfiguration.default
Expand All @@ -46,52 +45,59 @@ class Search: SearchProtocol {
}
}

func searchCompletionHandler(dataOptional: Data?, responseOptional: URLResponse?, errorOptional: Error?) {
if let error = errorOptional {
// MARK: - private

fileprivate func searchCompletionHandler(data: Data?, response: URLResponse?, error: Error?) {
if let error = error {
delegate?.searchError(messageText: "Search Error", informativeText: error.localizedDescription)
} else {
if let data = dataOptional {
parseSearchResult(data: data)
} else if let data = data {
do {
let jsonObject = try JSONSerialization.jsonObject(with: data) as! [String: AnyObject]
handle(jsonObject: jsonObject)
} catch {
delegate?.searchError(messageText: "Search Error", informativeText: "JSON decoding failed: \(error.localizedDescription)")
}
}
}

func parseSearchResult(data: Data) {
do {
let json = try JSONSerialization.jsonObject(with: data) as! [String: AnyObject]
parseSearchResult(json: json)
} catch {
delegate?.searchError(messageText: "Search Error", informativeText: "JSON decoding failed: \(error.localizedDescription)")
}
fileprivate func handle(jsonObject: [String: AnyObject]) {
let jsonSearchResults = extractJsonSearchResults(jsonObject)
var searchResults = extractSearchResults(jsonSearchResults)
sortByName(&searchResults)
delegate?.display(searchResults: searchResults) // done
}

func parseSearchResult(json: [String: AnyObject]) {

fileprivate func extractJsonSearchResults(_ json: [String : AnyObject]) -> NSArray {
var jsonSearchResults = NSArray()

if let totalCount = json["total_count"] as? Int {
if totalCount == 0 {
delegate?.resetSearchResults(statusText: "No search results")
} else {
for (key, value) in json {
if key == "items" {
if let itemsArray = value as? NSArray {
parseSearchResult(itemsArray: itemsArray)
if let jsonSearchResultsArray = value as? NSArray {
jsonSearchResults = jsonSearchResultsArray
}
}
}
}
} else {
delegate?.resetSearchResults(statusText: "No search results")
}

return jsonSearchResults
}

func parseSearchResult(itemsArray: NSArray) {
fileprivate func extractSearchResults(_ jsonSearchResultItemsArray: NSArray) -> [SearchResult] {
var searchResults: [SearchResult] = []

for itemArrayElement in itemsArray {
for itemArrayElement in jsonSearchResultItemsArray {
if let item = itemArrayElement as? [String: AnyObject] {
if let name = item["name"] as? String {
let suffix = name.substring(from: name.index(name.characters.endIndex, offsetBy: -3))
let suffix = name.substring(from: name.index(name.endIndex, offsetBy: -3))
if suffix == ".rb" {
let nameWithoutSuffix = name.substring(to: name.index(name.characters.endIndex, offsetBy: -3))
let nameWithoutSuffix = name.substring(to: name.index(name.endIndex, offsetBy: -3))

if let urlString = item["url"] as? String {
if let url = URL(string: urlString) {
Expand All @@ -104,15 +110,16 @@ class Search: SearchProtocol {
}
}

return searchResults
}

fileprivate func sortByName(_ searchResults: inout [SearchResult]) {
searchResults.sort { (a, b) -> Bool in
if a.name < b.name {
return true
} else {
return false
}
}

delegate?.display(searchResults: searchResults)
}

}
Loading

0 comments on commit 315d1a6

Please sign in to comment.