Ferrite: Format and cleanup

Also add swipe to delete support in source lists

Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
kingbri 2022-11-19 11:58:02 -05:00
parent a774564212
commit e063b91f3f
13 changed files with 158 additions and 149 deletions

View file

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
0C0167DC29293FA900B65783 /* RealDebridModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0167DB29293FA900B65783 /* RealDebridModels.swift */; };
0C0D50E5288DFE7F0035ECC8 /* SourceModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */; };
0C0D50E7288DFF850035ECC8 /* SourcesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50E6288DFF850035ECC8 /* SourcesView.swift */; };
0C10848B28BD9A38008F0BA6 /* SettingsAppVersionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C10848A28BD9A38008F0BA6 /* SettingsAppVersionView.swift */; };
@ -68,7 +69,6 @@
0CA148DB288903F000DE2211 /* NavView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148C1288903F000DE2211 /* NavView.swift */; };
0CA148DC288903F000DE2211 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0CA148C2288903F000DE2211 /* Assets.xcassets */; };
0CA148DD288903F000DE2211 /* ScrapingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148C3288903F000DE2211 /* ScrapingViewModel.swift */; };
0CA148DE288903F000DE2211 /* RealDebridModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148C4288903F000DE2211 /* RealDebridModels.swift */; };
0CA148DF288903F000DE2211 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0CA148C6288903F000DE2211 /* Preview Assets.xcassets */; };
0CA148E0288903F000DE2211 /* FerriteApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148C7288903F000DE2211 /* FerriteApp.swift */; };
0CA148E1288903F000DE2211 /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA148C9288903F000DE2211 /* Collection.swift */; };
@ -104,6 +104,7 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
0C0167DB29293FA900B65783 /* RealDebridModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealDebridModels.swift; sourceTree = "<group>"; };
0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceModels.swift; sourceTree = "<group>"; };
0C0D50E6288DFF850035ECC8 /* SourcesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourcesView.swift; sourceTree = "<group>"; };
0C10848A28BD9A38008F0BA6 /* SettingsAppVersionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAppVersionView.swift; sourceTree = "<group>"; };
@ -160,7 +161,6 @@
0CA148C1288903F000DE2211 /* NavView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavView.swift; sourceTree = "<group>"; };
0CA148C2288903F000DE2211 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
0CA148C3288903F000DE2211 /* ScrapingViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrapingViewModel.swift; sourceTree = "<group>"; };
0CA148C4288903F000DE2211 /* RealDebridModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 3; lastKnownFileType = sourcecode.swift; path = RealDebridModels.swift; sourceTree = "<group>"; };
0CA148C6288903F000DE2211 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
0CA148C7288903F000DE2211 /* FerriteApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FerriteApp.swift; sourceTree = "<group>"; };
0CA148C9288903F000DE2211 /* Collection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Collection.swift; sourceTree = "<group>"; };
@ -243,7 +243,7 @@
children = (
0C7ED14028D61BBA009E29AD /* BackupModels.swift */,
0C68135128BC1A7C00FAD890 /* GithubModels.swift */,
0CA148C4288903F000DE2211 /* RealDebridModels.swift */,
0C0167DB29293FA900B65783 /* RealDebridModels.swift */,
0C41BC6428C2AEB900B47DD6 /* SearchModels.swift */,
0C95D8D928A55BB6005E22B3 /* SettingsModels.swift */,
0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */,
@ -605,6 +605,7 @@
0C4CFC4E28970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift in Sources */,
0CDCB91828C662640098B513 /* EmptyInstructionView.swift in Sources */,
0CA148E2288903F000DE2211 /* Data.swift in Sources */,
0C0167DC29293FA900B65783 /* RealDebridModels.swift in Sources */,
0C57D4CC289032ED008534E8 /* SearchResultRDView.swift in Sources */,
0C41BC6328C2AD0F00B47DD6 /* SearchResultButtonView.swift in Sources */,
0CA05459288EE9E600850554 /* SourceManager.swift in Sources */,
@ -615,7 +616,6 @@
0CA05457288EE58200850554 /* SettingsSourceListView.swift in Sources */,
0C78041D28BFB3EA001E8CA3 /* String.swift in Sources */,
0C31133C28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift in Sources */,
0CA148DE288903F000DE2211 /* RealDebridModels.swift in Sources */,
0CBC76FF288DAAD00054BE44 /* NavigationViewModel.swift in Sources */,
0C44E2AD28D51C63007711AE /* BackupManager.swift in Sources */,
0C4CFC4D28970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift in Sources */,
@ -767,6 +767,7 @@
INFOPLIST_FILE = Ferrite/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Ferrite;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment";
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
@ -801,6 +802,7 @@
INFOPLIST_FILE = Ferrite/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Ferrite;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment";
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;

View file

@ -45,7 +45,7 @@ struct PersistenceController {
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
container.loadPersistentStores { _, error in
if let error = error {
if let error {
fatalError("CoreData init error: \(error)")
}
}
@ -203,7 +203,7 @@ struct PersistenceController {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "History")
if let predicate = predicate {
if let predicate {
fetchRequest.predicate = predicate
} else if range != .allTime {
throw HistoryDeleteError.noDate("No history date range was provided and you weren't trying to clear everything! Try relaunching the app?")

View file

@ -25,13 +25,13 @@ extension View {
// MARK: Modifiers
func conditionalContextMenu<InternalContent: View, ID: Hashable>(id: ID,
@ViewBuilder _ internalContent: @escaping () -> InternalContent) -> some View
func conditionalContextMenu(id: some Hashable,
@ViewBuilder _ internalContent: @escaping () -> some View) -> some View
{
modifier(ConditionalContextMenu(internalContent, id: id))
}
func conditionalId<ID: Hashable>(_ id: ID) -> some View {
func conditionalId(_ id: some Hashable) -> some View {
modifier(ConditionalId(id: id))
}

View file

@ -2,7 +2,7 @@
// RealDebridModels.swift
// Ferrite
//
// Created by Brian Dashore on 7/5/22.
// Created by Brian Dashore on 11/19/22.
//
// Structures generated from Quicktype
@ -11,184 +11,184 @@ import Foundation
// MARK: - device code endpoint
public struct DeviceCodeResponse: Codable, Sendable {
let deviceCode, userCode: String
let interval, expiresIn: Int
let verificationURL, directVerificationURL: String
let deviceCode, userCode: String
let interval, expiresIn: Int
let verificationURL, directVerificationURL: String
enum CodingKeys: String, CodingKey {
case deviceCode = "device_code"
case userCode = "user_code"
case interval
case expiresIn = "expires_in"
case verificationURL = "verification_url"
case directVerificationURL = "direct_verification_url"
}
enum CodingKeys: String, CodingKey {
case deviceCode = "device_code"
case userCode = "user_code"
case interval
case expiresIn = "expires_in"
case verificationURL = "verification_url"
case directVerificationURL = "direct_verification_url"
}
}
// MARK: - device credentials endpoint
public struct DeviceCredentialsResponse: Codable, Sendable {
let clientID, clientSecret: String?
let clientID, clientSecret: String?
enum CodingKeys: String, CodingKey {
case clientID = "client_id"
case clientSecret = "client_secret"
}
enum CodingKeys: String, CodingKey {
case clientID = "client_id"
case clientSecret = "client_secret"
}
}
// MARK: - token endpoint
public struct TokenResponse: Codable, Sendable {
let accessToken: String
let expiresIn: Int
let refreshToken, tokenType: String
let accessToken: String
let expiresIn: Int
let refreshToken, tokenType: String
enum CodingKeys: String, CodingKey {
case accessToken = "access_token"
case expiresIn = "expires_in"
case refreshToken = "refresh_token"
case tokenType = "token_type"
}
enum CodingKeys: String, CodingKey {
case accessToken = "access_token"
case expiresIn = "expires_in"
case refreshToken = "refresh_token"
case tokenType = "token_type"
}
}
// MARK: - instantAvailability endpoint
// Thanks Skitty!
public struct InstantAvailabilityResponse: Codable, Sendable {
var data: InstantAvailabilityData?
var data: InstantAvailabilityData?
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let data = try? container.decode(InstantAvailabilityData.self) {
self.data = data
}
}
}
struct InstantAvailabilityData: Codable, Sendable {
var rd: [[String: InstantAvailabilityInfo]]
}
struct InstantAvailabilityInfo: Codable, Sendable {
var filename: String
var filesize: Int
if let data = try? container.decode(InstantAvailabilityData.self) {
self.data = data
}
}
}
// MARK: - Instant Availability client side structures
struct InstantAvailabilityData: Codable, Sendable {
var rd: [[String: InstantAvailabilityInfo]]
}
struct InstantAvailabilityInfo: Codable, Sendable {
var filename: String
var filesize: Int
}
public struct RealDebridIA: Codable, Hashable, Sendable {
let hash: String
let expiryTimeStamp: Double
var files: [RealDebridIAFile] = []
var batches: [RealDebridIABatch] = []
let hash: String
let expiryTimeStamp: Double
var files: [RealDebridIAFile] = []
var batches: [RealDebridIABatch] = []
}
public struct RealDebridIABatch: Codable, Hashable, Sendable {
let files: [RealDebridIABatchFile]
let files: [RealDebridIABatchFile]
}
public struct RealDebridIABatchFile: Codable, Hashable, Sendable {
let id: Int
let fileName: String
let id: Int
let fileName: String
}
public struct RealDebridIAFile: Codable, Hashable, Sendable {
let name: String
let batchIndex: Int
let batchFileIndex: Int
let name: String
let batchIndex: Int
let batchFileIndex: Int
}
public enum RealDebridIAStatus: Codable, Hashable, Sendable {
case full
case partial
case none
case full
case partial
case none
}
// MARK: - addMagnet endpoint
public struct AddMagnetResponse: Codable, Sendable {
let id: String
let uri: String
let id: String
let uri: String
}
// MARK: - torrentInfo endpoint
struct TorrentInfoResponse: Codable, Sendable {
let id, filename, originalFilename, hash: String
let bytes, originalBytes: Int
let host: String
let split, progress: Int
let status, added: String
let files: [TorrentInfoFile]
let links: [String]
let ended: String?
let speed: Int?
let seeders: Int?
let id, filename, originalFilename, hash: String
let bytes, originalBytes: Int
let host: String
let split, progress: Int
let status, added: String
let files: [TorrentInfoFile]
let links: [String]
let ended: String?
let speed: Int?
let seeders: Int?
enum CodingKeys: String, CodingKey {
case id, filename
case originalFilename = "original_filename"
case hash, bytes
case originalBytes = "original_bytes"
case host, split, progress, status, added, files, links, ended, speed, seeders
}
enum CodingKeys: String, CodingKey {
case id, filename
case originalFilename = "original_filename"
case hash, bytes
case originalBytes = "original_bytes"
case host, split, progress, status, added, files, links, ended, speed, seeders
}
}
struct TorrentInfoFile: Codable, Sendable {
let id: Int
let path: String
let bytes, selected: Int
let id: Int
let path: String
let bytes, selected: Int
}
public struct UserTorrentsResponse: Codable, Sendable {
let id, filename, hash: String
let bytes: Int
let host: String
let split, progress: Int
let status, added: String
let links: [String]
let speed, seeders: Int?
let ended: String?
let id, filename, hash: String
let bytes: Int
let host: String
let split, progress: Int
let status, added: String
let links: [String]
let speed, seeders: Int?
let ended: String?
}
// MARK: - unrestrictLink endpoint
struct UnrestrictLinkResponse: Codable, Sendable {
let id, filename: String
let mimeType: String?
let filesize: Int
let link: String
let host: String
let hostIcon: String
let chunks, crc: Int
let download: String
let streamable: Int
let id, filename: String
let mimeType: String?
let filesize: Int
let link: String
let host: String
let hostIcon: String
let chunks, crc: Int
let download: String
let streamable: Int
enum CodingKeys: String, CodingKey {
case id, filename, mimeType, filesize, link, host
case hostIcon = "host_icon"
case chunks, crc, download, streamable
}
enum CodingKeys: String, CodingKey {
case id, filename, mimeType, filesize, link, host
case hostIcon = "host_icon"
case chunks, crc, download, streamable
}
}
// MARK: - User downloads list
public struct UserDownloadsResponse: Codable, Sendable {
let id, filename: String
let mimeType: String?
let filesize: Int
let link: String
let host: String
let hostIcon: String
let chunks: Int
let download: String
let streamable: Int
let generated: String
let id, filename: String
let mimeType: String?
let filesize: Int
let link: String
let host: String
let hostIcon: String
let chunks: Int
let download: String
let streamable: Int
let generated: String
enum CodingKeys: String, CodingKey {
case id, filename, mimeType, filesize, link, host
case hostIcon = "host_icon"
case chunks, download, streamable, generated
}
enum CodingKeys: String, CodingKey {
case id, filename, mimeType, filesize, link, host
case hostIcon = "host_icon"
case chunks, download, streamable, generated
}
}

View file

@ -177,7 +177,7 @@ public class BackupManager: ObservableObject {
do {
try FileManager.default.removeItem(at: backupUrl)
if let index = index {
if let index {
backupUrls.remove(at: index)
} else {
backupUrls.removeAll(where: { $0 == backupUrl })

View file

@ -82,7 +82,7 @@ public class DebridManager: ObservableObject {
}
public func matchSearchResult(result: SearchResult?) -> RealDebridIAStatus {
guard let result = result else {
guard let result else {
return .none
}

View file

@ -74,7 +74,7 @@ class ScrapingViewModel: ObservableObject {
fallbackUrls: source.fallbackUrls
)
if let data = data,
if let data,
let html = String(data: data, encoding: .utf8)
{
let sourceResults = await scrapeHtml(source: source, baseUrl: baseUrl, html: html)
@ -99,7 +99,7 @@ class ScrapingViewModel: ObservableObject {
)
}
if let data = data,
if let data,
let rss = String(data: data, encoding: .utf8)
{
let sourceResults = await scrapeRss(source: source, rss: rss)
@ -145,7 +145,7 @@ class ScrapingViewModel: ObservableObject {
fallbackUrls: source.fallbackUrls
)
if let data = data {
if let data {
let sourceResults = await scrapeJson(source: source, jsonData: data)
tempResults += sourceResults
}
@ -170,7 +170,7 @@ class ScrapingViewModel: ObservableObject {
return data
}
if let fallbackUrls = fallbackUrls {
if let fallbackUrls {
for fallbackUrl in fallbackUrls {
if let data = await fetchWebsiteData(urlString: fallbackUrl + replacedSearchUrl) {
return data
@ -359,7 +359,7 @@ class ScrapingViewModel: ObservableObject {
}
}
} else if
let searchResult = searchResult,
let searchResult,
let magnetLink = searchResult.magnetLink,
magnetLink.starts(with: "magnet:"),
!tempResults.contains(searchResult)
@ -404,7 +404,7 @@ class ScrapingViewModel: ObservableObject {
if let magnetLinkParser = jsonParser.magnetLink, existingSearchResult?.magnetLink == nil {
let rawLink = result[magnetLinkParser.query.components(separatedBy: ".")].rawValue
link = rawLink is NSNull ? nil : String(describing: rawLink)
} else if let magnetHash = magnetHash {
} else if let magnetHash {
link = generateMagnetLink(magnetHash: magnetHash, title: title, trackers: source.trackers)
}
@ -506,7 +506,7 @@ class ScrapingViewModel: ObservableObject {
discriminator: magnetLinkParser.discriminator,
regexString: magnetLinkParser.regex
)
} else if let magnetHash = magnetHash {
} else if let magnetHash {
link = generateMagnetLink(magnetHash: magnetHash, title: title, trackers: source.trackers)
} else {
continue
@ -586,7 +586,7 @@ class ScrapingViewModel: ObservableObject {
parsedValue = try item.getElementsByTag(query).first()?.text()
default:
// If there's a key/value to lookup the attribute with, query it. Othewise assume the value is in the same attribute
if let discriminator = discriminator {
if let discriminator {
let containerElement = try item.getElementsByAttributeValue(discriminator, query).first()
parsedValue = try containerElement?.attr(attribute)
} else {
@ -596,8 +596,8 @@ class ScrapingViewModel: ObservableObject {
}
// A capture group must be used in the provided regex
if let regexString = regexString,
let parsedValue = parsedValue,
if let regexString,
let parsedValue,
let regexValue = try? Regex(regexString).firstMatch(in: parsedValue)?.groups[safe: 0]?.value
{
return regexValue
@ -777,8 +777,8 @@ class ScrapingViewModel: ObservableObject {
}
// A capture group must be used in the provided regex
if let regexString = regexString,
let parsedValue = parsedValue,
if let regexString,
let parsedValue,
let regexValue = try? Regex(regexString).firstMatch(in: parsedValue)?.groups[safe: 0]?.value
{
return regexValue
@ -791,10 +791,10 @@ class ScrapingViewModel: ObservableObject {
public func fetchMagnetHash(magnetLink: String? = nil, existingHash: String? = nil) -> String? {
var magnetHash: String
if let existingHash = existingHash {
if let existingHash {
magnetHash = existingHash
} else if
let magnetLink = magnetLink,
let magnetLink,
let firstSplit = magnetLink.split(separator: ":")[safe: 3],
let tempHash = firstSplit.split(separator: "&")[safe: 0]
{
@ -839,11 +839,11 @@ class ScrapingViewModel: ObservableObject {
magnetLinkArray.append(magnetHash)
if let title = title, let encodedTitle = title.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) {
if let title, let encodedTitle = title.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) {
magnetLinkArray.append("&dn=\(encodedTitle)")
}
if let trackers = trackers {
if let trackers {
for trackerUrl in trackers {
if URL(string: trackerUrl) != nil,
let encodedUrlString = trackerUrl.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)

View file

@ -71,7 +71,7 @@ public class SourceManager: ObservableObject {
// Checks if the current app version is supported by the source
func checkAppVersion(minVersion: String?) -> Bool {
// If there's no min version, assume that every version is supported
guard let minVersion = minVersion else {
guard let minVersion else {
return true
}
@ -385,7 +385,7 @@ public class SourceManager: ObservableObject {
let (data, _) = try await URLSession.shared.data(for: URLRequest(url: URL(string: sourceUrl)!))
let rawResponse = try JSONDecoder().decode(SourceListJson.self, from: data)
if let existingSourceList = existingSourceList {
if let existingSourceList {
existingSourceList.urlString = sourceUrl
existingSourceList.name = rawResponse.name
existingSourceList.author = rawResponse.author

View file

@ -36,7 +36,7 @@ class ToastViewModel: ObservableObject {
@Published var showToast: Bool = false
public func updateToastDescription(_ description: String, newToastType: ToastType? = nil) {
if let newToastType = newToastType {
if let newToastType {
toastType = newToastType
}

View file

@ -37,7 +37,7 @@ struct AlertButton: Identifiable {
}
func toActionButton() -> Alert.Button {
if let role = role {
if let role {
switch role {
case .cancel:
return .cancel(Text(label))
@ -57,7 +57,7 @@ struct AlertButton: Identifiable {
@available(iOS 15.0, *)
func toButtonRole(_ role: Role?) -> ButtonRole? {
if let role = role {
if let role {
switch role {
case .destructive:
return .destructive

View file

@ -32,7 +32,7 @@ extension Backport where Content: View {
}
},
message: {
if let message = message {
if let message {
Text(message)
}
}
@ -75,7 +75,7 @@ extension Backport where Content: View {
button.toButtonView()
}
} message: {
if let message = message {
if let message {
Text(message)
}
}

View file

@ -64,7 +64,7 @@ struct ListRowTextView: View {
Spacer()
if let rightText = rightText {
if let rightText {
Text(rightText)
} else {
Image(systemName: rightSymbol!)

View file

@ -64,6 +64,13 @@ struct SettingsSourceListView: View {
}
}
}
.onDelete { offsets in
for index in offsets {
if let list = sourceLists[safe: index] {
PersistenceController.shared.delete(list, context: backgroundContext)
}
}
}
}
.listStyle(.insetGrouped)
.inlinedList()