Skip to content

Commit

Permalink
Check OpenSubsonic extensions, HTTP POST
Browse files Browse the repository at this point in the history
- Check for OpenSubsonic extensions. Right now, the only one we check
  for is HTTP POST support.
- Actually support HTTP POST. Most servers support this, but for now,
  we're being conservative and doing what OS suggests and check for the
  feature flag.
- Refactor the request operation heavily to deal with HTTP POST and
  deal with URL construction changes because of POST support.
  • Loading branch information
NattyNarwhal committed Feb 1, 2025
1 parent 9359f5d commit af69327
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 70 deletions.
4 changes: 4 additions & 0 deletions Submariner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
3E189E4028EF5BAB0062ACA0 /* SBAudioMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E189E3F28EF5BAB0062ACA0 /* SBAudioMetadata.swift */; };
3E1B785E2ACE5039008927C6 /* SBInspectorController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E1B785D2ACE5039008927C6 /* SBInspectorController.swift */; };
3E1C6C432A5264570085B578 /* SBOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E1C6C422A5264570085B578 /* SBOperation.swift */; };
3E2108122D4C7ECA00A1A4F2 /* Array+QueryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E2108112D4C7EC500A1A4F2 /* Array+QueryItem.swift */; };
3E2155122B26E6F0004BCCFC /* SBSubsonicRequestType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E2155112B26E6F0004BCCFC /* SBSubsonicRequestType.swift */; };
3E2155142B281E7D004BCCFC /* UTType+AudioToolbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E2155132B281E7D004BCCFC /* UTType+AudioToolbox.swift */; };
3E2E1C842A395B79001A3148 /* SBSubsonicRequestOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E2E1C832A395B79001A3148 /* SBSubsonicRequestOperation.swift */; };
Expand Down Expand Up @@ -186,6 +187,7 @@
3E189E3F28EF5BAB0062ACA0 /* SBAudioMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SBAudioMetadata.swift; sourceTree = "<group>"; };
3E1B785D2ACE5039008927C6 /* SBInspectorController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SBInspectorController.swift; sourceTree = "<group>"; };
3E1C6C422A5264570085B578 /* SBOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SBOperation.swift; sourceTree = "<group>"; };
3E2108112D4C7EC500A1A4F2 /* Array+QueryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+QueryItem.swift"; sourceTree = "<group>"; };
3E2155112B26E6F0004BCCFC /* SBSubsonicRequestType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SBSubsonicRequestType.swift; sourceTree = "<group>"; };
3E2155132B281E7D004BCCFC /* UTType+AudioToolbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UTType+AudioToolbox.swift"; sourceTree = "<group>"; };
3E2E1C832A395B79001A3148 /* SBSubsonicRequestOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SBSubsonicRequestOperation.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -596,6 +598,7 @@
4C87EDAC139CD9E90064DE2E /* Additions */ = {
isa = PBXGroup;
children = (
3E2108112D4C7EC500A1A4F2 /* Array+QueryItem.swift */,
3EF978032BC4659B00C986E9 /* Binding+Nil.swift */,
3E87E90F2B4364CF00E85000 /* Collection+IndexSet.swift */,
3E04F63A2B7CA48400E24E56 /* Data+Random.swift */,
Expand Down Expand Up @@ -866,6 +869,7 @@
3E32BE522B8D9FDE00E77CF0 /* SBLibraryItemPasteboardWriter.swift in Sources */,
3E1B785E2ACE5039008927C6 /* SBInspectorController.swift in Sources */,
3EF978022BC3C4E300C986E9 /* SBMessageTextView.swift in Sources */,
3E2108122D4C7ECA00A1A4F2 /* Array+QueryItem.swift in Sources */,
3E6DF3D72C3DEA6800055A52 /* SBPlayRateController.swift in Sources */,
3E70B2DF2A2BDC55002C0B93 /* SBApplication.swift in Sources */,
3E702DE72A428A1B005F7184 /* Synchronized.swift in Sources */,
Expand Down
25 changes: 25 additions & 0 deletions Submariner/Array+QueryItem.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// Array+QueryItem.swift
// Submariner
//
// Created by Calvin Buckley on 2025-01-30.
//
// Copyright (c) 2025 Calvin Buckley
// SPDX-License-Identifier: BSD-3-Clause
//

import Foundation

extension Array where Element == URLQueryItem {
// Dictionaries aren't the right fit because the API takes multiple items of same name for an array
// But it does mean that mapping it to a dictionary type access isn't ideal
// As such, first element wins for gets, and append for inserts
subscript(_ key: String) -> String? {
get {
self.first { $0.name == key }?.value
}
set {
self.append(URLQueryItem(name: key, value: newValue))
}
}
}
2 changes: 1 addition & 1 deletion Submariner/SBAppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fileprivate let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, catego
// #MARK: Init User Defaults
let defaults: [String: Any] = [
"clientIdentifier": "submariner",
"apiVersion": "1.15.0",
"apiVersion": "1.16.1",
"playerBehavior": NSNumber(value: 1),
"playerVolume": NSNumber(value: 0.5),
"repeatMode": NSNumber(value: SBPlayer.RepeatMode.no.rawValue),
Expand Down
2 changes: 2 additions & 0 deletions Submariner/SBDatabaseController.m
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,7 @@ - (void)reloadServerInternal: (SBServer*)server {
if (server == nil || ![server isKindOfClass:[SBServer class]]) {
return;
}
[server getOpenSubsonicExtensions];
[server getServerLicense];
[server getArtists];
[server getServerDirectories];
Expand Down Expand Up @@ -1449,6 +1450,7 @@ - (void)subsonicConnectionFailed:(NSNotification *)notification {

- (void)subsonicConnectionSucceeded:(NSNotification *)notification {
// loading of server content, major !!!
[self.server getOpenSubsonicExtensions];
[self.server getServerLicense];
[self.server getArtists];
[self.server getServerPlaylists];
Expand Down
20 changes: 20 additions & 0 deletions Submariner/SBServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public class SBServer: SBResource {
// This is NSNumber for Cocoa binding's sake
fileprivate static var _supportsNowPlaying: [NSManagedObjectID: NSNumber] = [:]
fileprivate static var _supportsPodcasts: [NSManagedObjectID: NSNumber] = [:]
fileprivate static var _supportsFormPost: [NSManagedObjectID: NSNumber] = [:]

@objc dynamic var supportsNowPlaying: NSNumber {
get {
Expand All @@ -58,6 +59,16 @@ public class SBServer: SBResource {
}
}

@objc dynamic var supportsFormPost: NSNumber {
get {
// we must prove it to be true, hence ?? false
return SBServer._supportsFormPost[self.objectID] ?? false
}
set {
SBServer._supportsFormPost[self.objectID] = newValue
}
}

func markNotSupported(feature: SBSubsonicRequestType) {
switch (feature) {
case .getNowPlaying:
Expand Down Expand Up @@ -357,6 +368,11 @@ public class SBServer: SBResource {
request.main()
}

@objc func getOpenSubsonicExtensions() {
let request = SBSubsonicRequestOperation(server: self, request: .getOpenSubsonicExtensions)
request.main()
}

@objc func getServerLicense() {
let request = SBSubsonicRequestOperation(server: self, request: .getLicense)
request.main()
Expand Down Expand Up @@ -400,6 +416,10 @@ public class SBServer: SBResource {
return parameters
}

func getBaseQueryItems() -> [URLQueryItem] {
return getBaseParameters().map { k, v in URLQueryItem(name: k, value: v) }
}

// #MARK: - Subsonic Client (Server Data)

@objc func getArtists() {
Expand Down
11 changes: 11 additions & 0 deletions Submariner/SBSubsonicParsingOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,17 @@ class SBSubsonicParsingOperation: SBOperation, XMLParserDelegate {
// nop
} else if elementName == "scanStatus" {
parseElementScanStatus(attributeDict: attributeDict)
} else if elementName == "openSubsonicExtensions" {
guard let name = attributeDict["name"] else {
return
}

logger.debug("Server supports feature \(name)")
if name == "formPost" {
server.supportsFormPost = true
}
} else if elementName == "versions" {
// nop
} else {
logger.error("Unknown XML element \(elementName, privacy: .public), attributes \(attributeDict, privacy: .public)")
}
Expand Down
Loading

0 comments on commit af69327

Please sign in to comment.