Skip to content

Commit

Permalink
CSV Import (#11)
Browse files Browse the repository at this point in the history
* Basic csv import

* Improved import and error handling

* Update xcode-build.yml

* Update xcode-build.yml

* Update SettingsView.swift

* Update Credits.rtf
  • Loading branch information
garamb1 authored May 12, 2024
1 parent 454bc29 commit 2688dd3
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 19 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/xcode-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest
- name: Clean, Build & Analyze
env:
scheme: Debug
run: |
Expand Down
27 changes: 26 additions & 1 deletion NetTuner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
030748BD2BCD8D980092FD7D /* RadioStation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030748BC2BCD8D980092FD7D /* RadioStation.swift */; };
030BE24A2BCEB624000BDF4B /* NSApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030BE2492BCEB624000BDF4B /* NSApplicationExtension.swift */; };
0338782F2BEF5191001CEB6F /* SwiftCSV in Frameworks */ = {isa = PBXBuildFile; productRef = 0338782E2BEF5191001CEB6F /* SwiftCSV */; };
035844722BCD2EF50021863E /* NetTunerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035844712BCD2EF50021863E /* NetTunerApp.swift */; };
035844742BCD2EF50021863E /* MenuBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035844732BCD2EF50021863E /* MenuBarView.swift */; };
035844762BCD2EF60021863E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 035844752BCD2EF60021863E /* Assets.xcassets */; };
Expand All @@ -21,6 +22,7 @@
/* Begin PBXFileReference section */
030748BC2BCD8D980092FD7D /* RadioStation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioStation.swift; sourceTree = "<group>"; };
030BE2492BCEB624000BDF4B /* NSApplicationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSApplicationExtension.swift; sourceTree = "<group>"; };
033878302BEF54B4001CEB6F /* NetTunerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NetTunerDebug.entitlements; sourceTree = "<group>"; };
0358446E2BCD2EF50021863E /* NetTuner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NetTuner.app; sourceTree = BUILT_PRODUCTS_DIR; };
035844712BCD2EF50021863E /* NetTunerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetTunerApp.swift; sourceTree = "<group>"; };
035844732BCD2EF50021863E /* MenuBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarView.swift; sourceTree = "<group>"; };
Expand All @@ -38,6 +40,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
0338782F2BEF5191001CEB6F /* SwiftCSV in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -96,6 +99,7 @@
035844702BCD2EF50021863E /* NetTuner */ = {
isa = PBXGroup;
children = (
033878302BEF54B4001CEB6F /* NetTunerDebug.entitlements */,
030BE2482BCEB619000BDF4B /* Extensions */,
030748AD2BCD89280092FD7D /* Data */,
030748AC2BCD89160092FD7D /* Views */,
Expand Down Expand Up @@ -135,6 +139,7 @@
);
name = NetTuner;
packageProductDependencies = (
0338782E2BEF5191001CEB6F /* SwiftCSV */,
);
productName = NetTuner;
productReference = 0358446E2BCD2EF50021863E /* NetTuner.app */;
Expand Down Expand Up @@ -165,6 +170,7 @@
);
mainGroup = 035844652BCD2EF50021863E;
packageReferences = (
0338782D2BEF5191001CEB6F /* XCRemoteSwiftPackageReference "SwiftCSV" */,
);
productRefGroup = 0358446F2BCD2EF50021863E /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -328,7 +334,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = NetTuner/NetTuner.entitlements;
CODE_SIGN_ENTITLEMENTS = NetTuner/NetTunerDebug.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
Expand Down Expand Up @@ -409,6 +415,25 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */

/* Begin XCRemoteSwiftPackageReference section */
0338782D2BEF5191001CEB6F /* XCRemoteSwiftPackageReference "SwiftCSV" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/swiftcsv/SwiftCSV.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.9.1;
};
};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
0338782E2BEF5191001CEB6F /* SwiftCSV */ = {
isa = XCSwiftPackageProductDependency;
package = 0338782D2BEF5191001CEB6F /* XCRemoteSwiftPackageReference "SwiftCSV" */;
productName = SwiftCSV;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 035844662BCD2EF50021863E /* Project object */;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"originHash" : "2adb555a53b5c7015c1913c95e2d6e5804f2075d934d4db6d3adc1da042d9b26",
"pins" : [
{
"identity" : "swiftcsv",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swiftcsv/SwiftCSV.git",
"state" : {
"revision" : "b61dcd1b2b1120e751cee4cd64a16ff10364885d",
"version" : "0.9.1"
}
}
],
"version" : 3
}
36 changes: 32 additions & 4 deletions NetTuner/Credits.rtf
Original file line number Diff line number Diff line change
@@ -1,11 +1,39 @@
{\rtf1\ansi\ansicpg1252\cocoartf2761
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;\red121\green121\blue121;}
{\*\expandedcolortbl;;\cssrgb\c54647\c54647\c54647;}
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fswiss\fcharset0 Helvetica-Bold;\f2\fmodern\fcharset0 Courier;
}
{\colortbl;\red255\green255\blue255;\red121\green121\blue121;\red0\green0\blue0;}
{\*\expandedcolortbl;;\cssrgb\c54647\c54647\c54647;\cssrgb\c0\c0\c0;}
\paperw12240\paperh15840\vieww25360\viewh25220\viewkind0
\pard\tx760\tx1235\pardirnatural\qc\partightenfactor0

\f0\fs22 \cf2 \
Programming\

\f1\b \cf3 Vincenzo Garambone\
{\field{\*\fldinst{HYPERLINK "https://garambo.it"}}{\fldrslt
\fs20 garambo.it}}
\f0\b0\fs20 \cf2 \
\pard\tx760\tx1235\pardirnatural\partightenfactor0

\f0\fs22 \cf2 \
\fs22 \cf2 \
\pard\tx760\tx1235\pardirnatural\qc\partightenfactor0
\cf2 Third Party Software\

\f1\b \cf3 SwiftCSV\
\pard\pardeftab720\qc\partightenfactor0
\fs20 \cf0 \expnd0\expndtw0\kerning0
\outl0\strokewidth0 \strokec3 Copyright (c) 2014 Naoto Kaneko.\
Copyright (c) 2019 SwiftCSV Contributors.
\f2\b0 \
\pard\tx760\tx1235\pardirnatural\qc\partightenfactor0
{\field{\*\fldinst{HYPERLINK "https://github.com/swiftcsv/SwiftCSV"}}{\fldrslt
\f1\b \cf3 \kerning1\expnd0\expndtw0 \outl0\strokewidth0 github.com/swiftcsv/SwiftCSV}}
\f1\b\fs22 \cf3 \kerning1\expnd0\expndtw0 \outl0\strokewidth0 \
\f0\b0 \cf2 \
\pard\tx760\tx1235\pardirnatural\partightenfactor0
\cf2 \
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 the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\
\
This program 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.\
Expand Down
16 changes: 16 additions & 0 deletions NetTuner/NetTunerDebug.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.downloads.read-only</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>
112 changes: 99 additions & 13 deletions NetTuner/Views/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import SwiftUI
import SwiftData
import SwiftCSV

struct SettingsView: View {

Expand All @@ -19,8 +20,9 @@ struct SettingsView: View {
@State private var radioSortOrder = [KeyPathComparator(\RadioStation.title)]
@State private var searchText: String = ""

// Addition popover
// Addition & import popover
@State private var showingAddPopover = false;
@State private var showingImportPopover = false;

var filteredRadios : [RadioStation] {
if searchText.count < 2 {
Expand Down Expand Up @@ -57,6 +59,13 @@ struct SettingsView: View {
deleteSelection()
}).disabled(radios.isEmpty)
}
ToolbarItem() {
Button("Add", systemImage: "square.and.arrow.down", action: {
showingImportPopover = true
}).popover(isPresented: $showingImportPopover, content: {
ImportView()
})
}
}
}

Expand Down Expand Up @@ -94,28 +103,105 @@ struct AddRadioView : View {
dismiss()
}).keyboardShortcut(.cancelAction)
Button("Add", action: {
let url = URL(string: urlString)
if (url != nil) {
let newRadio = RadioStation(url: url!, title: title)
addRadioStation(radioStation: newRadio)
resetInputs()
dismiss()
guard let url = URL(string: urlString), url.host != nil
else {
return
}
let newRadio = RadioStation(url: url, title: title)
modelContext.insert(newRadio)
})
.disabled(title.isEmpty && urlString.isEmpty)
.keyboardShortcut(.defaultAction)
}.padding()
}.frame(minWidth: 300)
}

}

struct ImportView : View {
@State private var importing = false
@State private var processing = false
@State private var doneProcessingMessage: String?


func addRadioStation(radioStation: RadioStation) {
modelContext.insert(radioStation)
@Environment(\.modelContext) var modelContext
@Environment(\.dismiss) var dismiss

var body: some View {
VStack {
Text("Import a CSV Radio List").font(.headline)
if processing {
HStack {
ProgressView()
}.padding()
.interactiveDismissDisabled()
} else if doneProcessingMessage != nil {
HStack {
Text(doneProcessingMessage!)
Button("OK", action: {
dismiss()
}).keyboardShortcut(.defaultAction)
}.padding()
.interactiveDismissDisabled()
} else {
HStack {
Text("Choose a file:")
Button("Open...") {
importing = true
}
.fileImporter(
isPresented: $importing,
allowedContentTypes: [.plainText, .commaSeparatedText]) { result in
switch result {
case .success(let file):
processing = true
loadFromCsv(fileUrl: file)
case .failure:
processing = false
doneProcessingMessage = "Could not read the CSV file."
}
}
}.padding()
}
}.padding()
}

func resetInputs() {
title = ""
urlString = ""
func loadFromCsv(fileUrl: URL) {
var toAdd : Set<RadioStation> = Set()
var parsingErrors = 0

do {
let csvFile: CSV = try CSV<Named>(url:fileUrl)
for row in csvFile.rows {

guard let itemUrl = row["url"], let itemTitle = row["title"]
else {
parsingErrors += 1
continue
}

guard let url = URL(string: itemUrl), url.host != nil
else {
parsingErrors += 1
continue
}
toAdd.insert(RadioStation(url: url, title: itemTitle))
}
} catch {
processing = false
doneProcessingMessage = "Could not read the CSV file."
return
}

for newRadio in toAdd {
modelContext.insert(newRadio)
}

processing = false
doneProcessingMessage = "Found \(toAdd.count) entries in file"

if parsingErrors > 0 {
doneProcessingMessage! += "\n\(parsingErrors) could not be added."
}
}
}

Expand Down

0 comments on commit 2688dd3

Please sign in to comment.