Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSV Import #11

Merged
merged 6 commits into from
May 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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