mirror of
https://github.com/danbee/persephone
synced 2025-03-04 08:39:11 +00:00
Handle connection/disconnection better
* Add menu options to connect/disconnect * Add option to attempt reconnection on fatal error * Add ability to reset connection to MPDClient
This commit is contained in:
parent
b46cbf229f
commit
c4e5f7408a
@ -16,19 +16,21 @@ class AppDelegate: NSObject,
|
||||
MediaKeyTapDelegate {
|
||||
var mediaKeyTap: MediaKeyTap?
|
||||
|
||||
@IBOutlet weak var connectMenuItem: NSMenuItem!
|
||||
@IBOutlet weak var disconnectMenuItem: NSMenuItem!
|
||||
@IBOutlet weak var mainWindowMenuItem: NSMenuItem!
|
||||
@IBOutlet weak var updateDatabaseMenuItem: NSMenuItem!
|
||||
@IBOutlet weak var playSelectedSongMenuItem: NSMenuItem!
|
||||
@IBOutlet weak var playSelectedSongNextMenuItem: NSMenuItem!
|
||||
@IBOutlet weak var addSelectedSongToQueueMenuItem: NSMenuItem!
|
||||
|
||||
|
||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||
mediaKeyTap = MediaKeyTap(delegate: self)
|
||||
mediaKeyTap?.start()
|
||||
|
||||
App.store.subscribe(self) {
|
||||
$0.select {
|
||||
$0.uiState
|
||||
($0.serverState, $0.uiState)
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,6 +107,11 @@ class AppDelegate: NSObject,
|
||||
playSelectedSongNextMenuItem.isEnabled = selectedSong != nil
|
||||
addSelectedSongToQueueMenuItem.isEnabled = selectedSong != nil
|
||||
}
|
||||
|
||||
func setConnectMenuItemsState(connected: Bool) {
|
||||
connectMenuItem.isEnabled = !connected
|
||||
disconnectMenuItem.isEnabled = connected
|
||||
}
|
||||
|
||||
func handle(mediaKey: MediaKey, event: KeyEvent) {
|
||||
switch mediaKey {
|
||||
@ -117,6 +124,13 @@ class AppDelegate: NSObject,
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func connectMenuAction(_ sender: NSMenuItem) {
|
||||
App.mpdServerController.connect()
|
||||
}
|
||||
@IBAction func disconnectMenuAction(_ sender: NSMenuItem) {
|
||||
App.mpdServerController.disconnect()
|
||||
}
|
||||
|
||||
@IBAction func updateDatabase(_ sender: NSMenuItem) {
|
||||
App.mpdClient.updateDatabase()
|
||||
}
|
||||
@ -182,11 +196,14 @@ class AppDelegate: NSObject,
|
||||
}
|
||||
|
||||
extension AppDelegate: StoreSubscriber {
|
||||
typealias StoreSubscriberStateType = UIState
|
||||
typealias StoreSubscriberStateType = (
|
||||
serverState: ServerState, uiState: UIState
|
||||
)
|
||||
|
||||
func newState(state: UIState) {
|
||||
updateDatabaseMenuItem.isEnabled = !state.databaseUpdating
|
||||
setMainWindowStateMenuItem(state: state.mainWindowState)
|
||||
setSongMenuItemsState(selectedSong: state.selectedSong)
|
||||
func newState(state: StoreSubscriberStateType) {
|
||||
updateDatabaseMenuItem.isEnabled = !state.uiState.databaseUpdating
|
||||
setMainWindowStateMenuItem(state: state.uiState.mainWindowState)
|
||||
setSongMenuItemsState(selectedSong: state.uiState.selectedSong)
|
||||
setConnectMenuItemsState(connected: state.serverState.connected)
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
<items>
|
||||
<menuItem title="Persephone" id="1Xt-HY-uBw">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Persephone" systemMenu="apple" id="uQy-DD-JDr">
|
||||
<menu key="submenu" title="Persephone" systemMenu="apple" autoenablesItems="NO" id="uQy-DD-JDr">
|
||||
<items>
|
||||
<menuItem title="About Persephone" id="5kV-Vb-QxS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
@ -29,7 +29,19 @@
|
||||
<segue destination="xYu-7w-E5x" kind="show" identifier="Preferences" id="OTW-56-v9E"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
|
||||
<menuItem title="Connect" keyEquivalent="c" id="VlA-Re-OZs">
|
||||
<modifierMask key="keyEquivalentModifierMask" control="YES" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="connectMenuAction:" target="Voe-Tx-rLC" id="Psl-MI-Dbx"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Disconnect" enabled="NO" keyEquivalent="d" id="yjK-qU-C6d">
|
||||
<modifierMask key="keyEquivalentModifierMask" control="YES" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="disconnectMenuAction:" target="Voe-Tx-rLC" id="smu-DN-Ubp"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="vcx-U6-vzu"/>
|
||||
<menuItem title="Services" id="NMo-om-nkz">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
|
||||
@ -178,6 +190,8 @@
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Persephone" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="addSelectedSongToQueueMenuItem" destination="JFH-jT-sBp" id="9dy-sJ-XYS"/>
|
||||
<outlet property="connectMenuItem" destination="VlA-Re-OZs" id="Zzt-yq-mtp"/>
|
||||
<outlet property="disconnectMenuItem" destination="yjK-qU-C6d" id="qel-dM-Gbl"/>
|
||||
<outlet property="mainWindowMenuItem" destination="1Sq-L7-znT" id="dC6-yY-6Ss"/>
|
||||
<outlet property="playSelectedSongMenuItem" destination="dyT-9E-DRY" id="UY2-SN-YMF"/>
|
||||
<outlet property="playSelectedSongNextMenuItem" destination="Q8j-jr-IOp" id="Jqh-ia-sMK"/>
|
||||
@ -208,7 +222,7 @@
|
||||
<rect key="frame" x="0.0" y="14" width="153" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<segmentedCell key="cell" borderStyle="border" alignment="left" style="texturedRounded" trackingMode="momentary" id="EBk-sD-nG7">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<segments>
|
||||
<segment image="prevTrackButton" width="32" enabled="NO"/>
|
||||
<segment image="playButton" width="48" enabled="NO" tag="1"/>
|
||||
@ -230,7 +244,7 @@
|
||||
<rect key="frame" x="16" y="14" width="55" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" placeholderString="8:88:88" id="g0c-k5-wCA">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
@ -257,7 +271,7 @@
|
||||
<rect key="frame" x="16" y="14" width="60" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="left" placeholderString="-8:88:88" id="XUa-pD-s5c">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
@ -279,7 +293,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="shuffleButton" imagePosition="overlaps" alignment="center" lineBreakMode="truncatingTail" borderStyle="border" inset="2" id="YNb-hd-ax8">
|
||||
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="handleShuffleButton:" target="B8D-0N-5wS" id="THd-0g-fmb"/>
|
||||
@ -295,7 +309,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="repeatButton" imagePosition="only" alignment="center" lineBreakMode="truncatingTail" borderStyle="border" inset="2" id="1bu-vK-3Hb">
|
||||
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="handleRepeatButton:" target="B8D-0N-5wS" id="EN2-u4-DNl"/>
|
||||
@ -308,7 +322,7 @@
|
||||
<rect key="frame" x="0.0" y="14" width="96" height="22"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" id="F3N-3P-tS3">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</searchFieldCell>
|
||||
@ -324,7 +338,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="speakerHigh" imagePosition="overlaps" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="bJh-X9-7q0">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="showVolumeControl:" target="B8D-0N-5wS" id="UoW-fa-jBM"/>
|
||||
@ -426,7 +440,7 @@
|
||||
<tabView key="tabView" type="noTabsNoBorder" id="6dC-M0-oC5">
|
||||
<rect key="frame" x="0.0" y="0.0" width="418" height="300"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="zhe-qh-Mal" id="LUL-qN-JlP"/>
|
||||
</connections>
|
||||
@ -464,7 +478,7 @@
|
||||
<rect key="frame" x="78" y="59" width="271" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Fetch missing artwork from MusicBrainz" bezelStyle="regularSquare" imagePosition="left" enabled="NO" inset="2" id="LpD-Ew-HMd">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="updateFetchMissingArtworkFromInternet:" target="3C9-vU-zjZ" id="I7x-9V-xJr"/>
|
||||
@ -476,7 +490,7 @@
|
||||
<rect key="frame" x="74" y="19" width="279" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Clear album art cache..." bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="l81-SG-7mf">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="clearAlbumArtCache:" target="3C9-vU-zjZ" id="tXg-rz-lvh"/>
|
||||
@ -512,7 +526,7 @@
|
||||
<subviews>
|
||||
<tabView type="noTabsNoBorder" initialItem="XgS-cX-SDH" translatesAutoresizingMaskIntoConstraints="NO" id="ARv-cj-xlz">
|
||||
<rect key="frame" x="0.0" y="0.0" width="478" height="558"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<tabViewItems>
|
||||
<tabViewItem label="Albums" identifier="" id="XgS-cX-SDH">
|
||||
<view key="view" id="hB7-hN-SbB">
|
||||
@ -571,7 +585,7 @@
|
||||
<textField key="contentView" translatesAutoresizingMaskIntoConstraints="NO" id="kvB-99-zwY">
|
||||
<rect key="frame" x="78" y="64" width="165" height="17"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Server Host:" id="AVi-g9-Irz">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
@ -594,7 +608,7 @@
|
||||
<textField key="contentView" translatesAutoresizingMaskIntoConstraints="NO" id="AU9-wN-kbU">
|
||||
<rect key="frame" x="78" y="29" width="165" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Server Port:" id="DgA-xT-2ir">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
@ -608,7 +622,7 @@
|
||||
<real key="minimum" value="0.0"/>
|
||||
<real key="maximum" value="65535"/>
|
||||
</numberFormatter>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
@ -634,7 +648,7 @@
|
||||
</viewController>
|
||||
<customObject id="lzf-yO-5pP" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
<textFieldCell lineBreakMode="clipping" alignment="right" title="Server Port:" id="22M-hh-h8g">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
@ -669,7 +683,7 @@
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="99v-Rb-3kv">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
@ -714,7 +728,7 @@
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="zb2-QK-DhK">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
@ -735,7 +749,7 @@
|
||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="i0h-bn-auJ" userLabel="Song Title View">
|
||||
<rect key="frame" x="1" y="23" width="217" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Song Title" id="ei8-1e-ErK">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
@ -762,7 +776,7 @@
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="JOa-Mc-ceQ">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
@ -775,7 +789,7 @@
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="ukg-c0-XVS">
|
||||
<rect key="frame" x="11" y="13" width="41" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" alignment="right" title="88:88" id="JnJ-sF-vCP">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
|
||||
@ -165,13 +165,28 @@ class WindowController: NSWindowController {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let alert = NSAlert(error: error)
|
||||
alert.informativeText = error.message
|
||||
alert.messageText = error.message
|
||||
|
||||
alert.alertStyle = error.recovered ? .warning : .critical
|
||||
|
||||
if !error.recovered {
|
||||
alert.addButton(withTitle: "Reconnect")
|
||||
alert.addButton(withTitle: "Dismiss")
|
||||
}
|
||||
|
||||
guard let window = NSApplication.shared.mainWindow
|
||||
guard let window = NSApplication.shared.mainWindow ?? self.window
|
||||
else { return }
|
||||
alert.beginSheetModal(for: window) { _ in }
|
||||
|
||||
alert.beginSheetModal(for: window) { response in
|
||||
switch response {
|
||||
case .alertFirstButtonReturn:
|
||||
if !error.recovered {
|
||||
App.mpdServerController.connect()
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,6 +13,8 @@ extension MPDClient {
|
||||
command: MPDCommand,
|
||||
userData: Dictionary<String, Any> = [:]
|
||||
) {
|
||||
guard command == .connect || isConnected else { return }
|
||||
|
||||
switch command {
|
||||
|
||||
case .connect:
|
||||
|
||||
@ -53,4 +53,10 @@ extension MPDClient {
|
||||
func disconnect() {
|
||||
enqueueCommand(command: .disconnect)
|
||||
}
|
||||
|
||||
func resetConnection() {
|
||||
delegate?.willDisconnect(mpdClient: self)
|
||||
mpd_connection_free(connection)
|
||||
self.isConnected = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +34,10 @@ extension MPDClient {
|
||||
message: message
|
||||
)
|
||||
delegate?.didRaiseError(mpdClient: self, error: error)
|
||||
|
||||
if !recovered {
|
||||
resetConnection()
|
||||
}
|
||||
|
||||
return recovered
|
||||
}
|
||||
|
||||
@ -11,6 +11,8 @@ import mpdclient
|
||||
|
||||
extension MPDClient {
|
||||
func noIdle() {
|
||||
guard isConnected else { return }
|
||||
|
||||
do {
|
||||
idleLock.lock()
|
||||
defer { idleLock.unlock() }
|
||||
@ -22,6 +24,8 @@ extension MPDClient {
|
||||
}
|
||||
|
||||
func idle(_ force: Bool = false) {
|
||||
guard isConnected else { return }
|
||||
|
||||
let shouldIdle: Bool
|
||||
|
||||
do {
|
||||
@ -50,8 +54,8 @@ extension MPDClient {
|
||||
wasIdle = isIdle
|
||||
isIdle = false
|
||||
}
|
||||
|
||||
if wasIdle {
|
||||
|
||||
if checkError() && wasIdle {
|
||||
if mpdIdle.contains(.database) {
|
||||
self.fetchAllAlbums()
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user