mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-03-13 11:16:36 +00:00
improved on/off detection added playlists added sync --This line, and
those below, will be ignored-- M 97_SB_SERVER.pm git-svn-id: https://svn.fhem.de/fhem/trunk@6212 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
9e17d08593
commit
a1e2a2ee2a
@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# This is absolutley open source. Please feel free to use just as you
|
# This is absolutley open source. Please feel free to use just as you
|
||||||
# like. Please note, that no warranty is given and no liability
|
# like. Please note, that no warranty is given and no liability
|
||||||
# granted.
|
# granted
|
||||||
#
|
#
|
||||||
# ############################################################################
|
# ############################################################################
|
||||||
#
|
#
|
||||||
@ -149,6 +149,12 @@ sub SB_SERVER_Define( $$ ) {
|
|||||||
|
|
||||||
$hash->{LASTANSWER} = "none";
|
$hash->{LASTANSWER} = "none";
|
||||||
|
|
||||||
|
# used for alive checking of the CLI interface
|
||||||
|
$hash->{ALIVECHECK} = "?";
|
||||||
|
|
||||||
|
# the status of the CLI connection (on / off)
|
||||||
|
$hash->{CLICONNECTION} = "?";
|
||||||
|
|
||||||
# preset our attributes
|
# preset our attributes
|
||||||
if( !defined( $attr{$name}{alivetimer} ) ) {
|
if( !defined( $attr{$name}{alivetimer} ) ) {
|
||||||
$attr{$name}{alivetimer} = 120;
|
$attr{$name}{alivetimer} = 120;
|
||||||
@ -187,12 +193,6 @@ sub SB_SERVER_Define( $$ ) {
|
|||||||
$hash->{READINGS}{serversecure}{TIME} = $tn;
|
$hash->{READINGS}{serversecure}{TIME} = $tn;
|
||||||
}
|
}
|
||||||
|
|
||||||
# the status of our server alive check mechanism
|
|
||||||
if( !defined( $hash->{READINGS}{alivecheck}{VAL} ) ) {
|
|
||||||
$hash->{READINGS}{alivecheck}{VAL} = "?";
|
|
||||||
$hash->{READINGS}{alivecheck}{TIME} = $tn;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# the maximum number of favorites on the server
|
# the maximum number of favorites on the server
|
||||||
if( !defined( $hash->{READINGS}{favoritestotal}{VAL} ) ) {
|
if( !defined( $hash->{READINGS}{favoritestotal}{VAL} ) ) {
|
||||||
@ -371,12 +371,13 @@ sub SB_SERVER_Ready( $ ) {
|
|||||||
SB_SERVER_Broadcast( $hash, "SERVER", "OFF" );
|
SB_SERVER_Broadcast( $hash, "SERVER", "OFF" );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( $hash->{TCPDev} ) {
|
return( DevIo_OpenDev( $hash, 1, "SB_SERVER_DoInit") );
|
||||||
SB_SERVER_DoInit( $hash );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return( DevIo_OpenDev( $hash, 1, "SB_SERVER_DoInit" ) );
|
#if( $hash->{TCPDev} ) {
|
||||||
|
#SB_SERVER_DoInit( $hash );
|
||||||
|
#}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -405,7 +406,7 @@ sub SB_SERVER_Attr( @ ) {
|
|||||||
my $name = shift( @_ );
|
my $name = shift( @_ );
|
||||||
my @args = @_;
|
my @args = @_;
|
||||||
|
|
||||||
Log( 1, "SB_SERVER_Attr: called with @args" );
|
Log( 4, "SB_SERVER_Attr: called with @args" );
|
||||||
|
|
||||||
if( $cmd eq "set" ) {
|
if( $cmd eq "set" ) {
|
||||||
if( $args[ 0 ] eq "alivetimer" ) {
|
if( $args[ 0 ] eq "alivetimer" ) {
|
||||||
@ -461,7 +462,12 @@ sub SB_SERVER_Set( $@ ) {
|
|||||||
DevIo_SimpleWrite( $hash, "listen 0\n", 0 );
|
DevIo_SimpleWrite( $hash, "listen 0\n", 0 );
|
||||||
|
|
||||||
} elsif( $cmd eq "statusRequest" ) {
|
} elsif( $cmd eq "statusRequest" ) {
|
||||||
|
Log3( $hash, 5, "SB_SERVER_Set: statusRequest" );
|
||||||
DevIo_SimpleWrite( $hash, "serverstatus 0 200\n", 0 );
|
DevIo_SimpleWrite( $hash, "serverstatus 0 200\n", 0 );
|
||||||
|
DevIo_SimpleWrite( $hash, "favorites items 0 " .
|
||||||
|
AttrVal( $name, "maxfavorites", 100 ) . "\n",
|
||||||
|
0 );
|
||||||
|
DevIo_SimpleWrite( $hash, "playlists 0 200\n", 0 );
|
||||||
|
|
||||||
} elsif( $cmd eq "cliraw" ) {
|
} elsif( $cmd eq "cliraw" ) {
|
||||||
# write raw messages to the CLI interface per player
|
# write raw messages to the CLI interface per player
|
||||||
@ -610,11 +616,19 @@ sub SB_SERVER_DoInit( $ ) {
|
|||||||
|
|
||||||
if( !$hash->{TCPDev} ) {
|
if( !$hash->{TCPDev} ) {
|
||||||
Log3( $hash, 5, "SB_SERVER_DoInit: no TCPDev available?" );
|
Log3( $hash, 5, "SB_SERVER_DoInit: no TCPDev available?" );
|
||||||
|
DevIo_CloseDev( $hash );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( $hash->{STATE} eq "disconnected" ) {
|
if( $hash->{STATE} eq "disconnected" ) {
|
||||||
# server is off after FHEM start, broadcast to clients
|
# server is off after FHEM start, broadcast to clients
|
||||||
|
if( ( ReadingsVal( $name, "power", "on" ) eq "on" ) ||
|
||||||
|
( ReadingsVal( $name, "power", "on" ) eq "?" ) ) {
|
||||||
|
# obviously the first we realize the Server is off
|
||||||
|
readingsSingleUpdate( $hash, "power", "off", 1 );
|
||||||
|
|
||||||
|
# and signal to our clients
|
||||||
SB_SERVER_Broadcast( $hash, "SERVER", "OFF" );
|
SB_SERVER_Broadcast( $hash, "SERVER", "OFF" );
|
||||||
|
}
|
||||||
return( "" );
|
return( "" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -627,9 +641,10 @@ sub SB_SERVER_DoInit( $ ) {
|
|||||||
DevIo_SimpleWrite( $hash, "serverstatus 0 200\n", 0 );
|
DevIo_SimpleWrite( $hash, "serverstatus 0 200\n", 0 );
|
||||||
DevIo_SimpleWrite( $hash, "favorites items 0 " .
|
DevIo_SimpleWrite( $hash, "favorites items 0 " .
|
||||||
AttrVal( $name, "maxfavorites", 100 ) . "\n", 0 );
|
AttrVal( $name, "maxfavorites", 100 ) . "\n", 0 );
|
||||||
|
DevIo_SimpleWrite( $hash, "playlists 0 200\n", 0 );
|
||||||
|
|
||||||
# start the alive checking mechanism
|
# start the alive checking mechanism
|
||||||
readingsSingleUpdate( $hash, "alivecheck", "?", 0 );
|
$hash->{ALIVECHECK} = "?";
|
||||||
InternalTimer( gettimeofday() + AttrVal( $name, "alivetimer", 120 ),
|
InternalTimer( gettimeofday() + AttrVal( $name, "alivetimer", 120 ),
|
||||||
"SB_SERVER_Alive",
|
"SB_SERVER_Alive",
|
||||||
$hash,
|
$hash,
|
||||||
@ -707,7 +722,7 @@ sub SB_SERVER_ParseCmds( $$ ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} elsif( $cmd eq "fhemalivecheck" ) {
|
} elsif( $cmd eq "fhemalivecheck" ) {
|
||||||
readingsSingleUpdate( $hash, "alivecheck", "received", 0 );
|
$hash->{ALIVECHECK} = "received";
|
||||||
Log3( $hash, 4, "SB_SERVER_ParseCmds($name): alivecheck received" );
|
Log3( $hash, 4, "SB_SERVER_ParseCmds($name): alivecheck received" );
|
||||||
|
|
||||||
} elsif( $cmd eq "favorites" ) {
|
} elsif( $cmd eq "favorites" ) {
|
||||||
@ -728,6 +743,10 @@ sub SB_SERVER_ParseCmds( $$ ) {
|
|||||||
Log3( $hash, 4, "SB_SERVER_ParseCmds($name): server status" );
|
Log3( $hash, 4, "SB_SERVER_ParseCmds($name): server status" );
|
||||||
SB_SERVER_ParseServerStatus( $hash, \@args );
|
SB_SERVER_ParseServerStatus( $hash, \@args );
|
||||||
|
|
||||||
|
} elsif( $cmd eq "playlists" ) {
|
||||||
|
Log3( $hash, 4, "SB_SERVER_ParseCmds($name): playlists" );
|
||||||
|
SB_SERVER_ParseServerPlaylists( $hash, \@args );
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
# unkown
|
# unkown
|
||||||
}
|
}
|
||||||
@ -741,6 +760,10 @@ sub SB_SERVER_Alive( $ ) {
|
|||||||
my ($hash) = @_;
|
my ($hash) = @_;
|
||||||
my $name = $hash->{NAME};
|
my $name = $hash->{NAME};
|
||||||
|
|
||||||
|
my $rccstatus = "on";
|
||||||
|
my $pingstatus = "on";
|
||||||
|
my $nexttime = gettimeofday() + AttrVal( $name, "alivetimer", 120 );
|
||||||
|
|
||||||
Log3( $hash, 4, "SB_SERVER_Alive($name): called" );
|
Log3( $hash, 4, "SB_SERVER_Alive($name): called" );
|
||||||
|
|
||||||
if( AttrVal( $name, "doalivecheck", "false" ) eq "false" ) {
|
if( AttrVal( $name, "doalivecheck", "false" ) eq "false" ) {
|
||||||
@ -748,29 +771,48 @@ sub SB_SERVER_Alive( $ ) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
# let's ping the server to figure out if he is reachable
|
# check via the RCC element
|
||||||
# needed for servers that go in hibernate mode
|
if( $hash->{RCCNAME} ne "none" ) {
|
||||||
|
# an RCC element has been given as argument
|
||||||
|
$rccstatus = ReadingsVal( $hash->{RCCNAME}, "state", "off" );
|
||||||
|
}
|
||||||
|
|
||||||
|
# check via ping
|
||||||
my $p = Net::Ping->new( 'tcp' );
|
my $p = Net::Ping->new( 'tcp' );
|
||||||
if( $p->ping( $hash->{IP}, 2 ) ) {
|
if( $p->ping( $hash->{IP}, 2 ) ) {
|
||||||
# host is reachable so go on normally
|
$pingstatus = "on";
|
||||||
|
} else {
|
||||||
|
$pingstatus = "off";
|
||||||
|
}
|
||||||
|
# close our ping mechanism again
|
||||||
|
$p->close( );
|
||||||
|
|
||||||
|
|
||||||
|
# set the status of the server accordingly
|
||||||
|
if( ( $rccstatus eq "on" ) || ( $pingstatus eq "on" ) ) {
|
||||||
|
# the server is reachable
|
||||||
if( ReadingsVal( $name, "power", "on" ) eq "off" ) {
|
if( ReadingsVal( $name, "power", "on" ) eq "off" ) {
|
||||||
Log3( $hash, 5, "SB_SERVER_Alive($name): ping succesful. " .
|
# the first time we see the server being on
|
||||||
|
Log3( $hash, 5, "SB_SERVER_Alive($name): " .
|
||||||
"SB-Server is back again." );
|
"SB-Server is back again." );
|
||||||
# first time we realized server is away
|
# first time we realized server is away
|
||||||
DevIo_OpenDev( $hash, 1, "SB_SERVER_DoInit" );
|
DevIo_OpenDev( $hash, 1, "SB_SERVER_DoInit" );
|
||||||
|
|
||||||
readingsSingleUpdate( $hash, "power", "on", 1 );
|
readingsSingleUpdate( $hash, "power", "on", 1 );
|
||||||
readingsSingleUpdate( $hash, "alivecheck", "?", 0 );
|
$hash->{ALIVECHECK} = "?";
|
||||||
# signal that to our clients
|
$hash->{CLICONNECTION} = "off";
|
||||||
SB_SERVER_Broadcast( $hash, "SERVER", "ON" );
|
|
||||||
|
# quicker update to capture CLI connection faster
|
||||||
|
$nexttime = gettimeofday() + 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( ReadingsVal( $name, "alivecheck", "received" ) eq "waiting" ) {
|
# check the CLI connection (sub-state)
|
||||||
|
if( $hash->{ALIVECHECK} eq "waiting" ) {
|
||||||
# ups, we did not receive any answer in the last minutes
|
# ups, we did not receive any answer in the last minutes
|
||||||
# SB Server potentially dead or shut-down
|
# SB Server potentially dead or shut-down
|
||||||
Log3( $hash, 5, "SB_SERVER_Alive($name): overrun SB-Server dead." );
|
Log3( $hash, 5, "SB_SERVER_Alive($name): overrun SB-Server dead." );
|
||||||
|
|
||||||
readingsSingleUpdate( $hash, "power", "off", 1 );
|
$hash->{CLICONNECTION} = "off";
|
||||||
readingsSingleUpdate( $hash, "alivecheck", "?", 0 );
|
|
||||||
|
|
||||||
# signal that to our clients
|
# signal that to our clients
|
||||||
SB_SERVER_Broadcast( $hash, "SERVER", "OFF" );
|
SB_SERVER_Broadcast( $hash, "SERVER", "OFF" );
|
||||||
@ -781,22 +823,32 @@ sub SB_SERVER_Alive( $ ) {
|
|||||||
# remove all timers we created
|
# remove all timers we created
|
||||||
RemoveInternalTimer( $hash );
|
RemoveInternalTimer( $hash );
|
||||||
} else {
|
} else {
|
||||||
|
if( $hash->{CLICONNECTION} eq "off" ) {
|
||||||
|
# signal that to our clients
|
||||||
|
# to be revisited, should only be sent after CLI established
|
||||||
|
SB_SERVER_Broadcast( $hash, "SERVER", "ON" );
|
||||||
|
}
|
||||||
|
|
||||||
|
$hash->{CLICONNECTION} = "on";
|
||||||
|
|
||||||
# just send something to the SB-Server. It will echo it
|
# just send something to the SB-Server. It will echo it
|
||||||
# if we receive the echo, the server is still alive
|
# if we receive the echo, the server is still alive
|
||||||
DevIo_SimpleWrite( $hash, "fhemalivecheck\n", 0 );
|
DevIo_SimpleWrite( $hash, "fhemalivecheck\n", 0 );
|
||||||
|
|
||||||
readingsSingleUpdate( $hash, "alivecheck", "waiting", 0 );
|
$hash->{ALIVECHECK} = "waiting";
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
|
||||||
# the server is away and therefore presumably in hibernate / suspend
|
} elsif( ( $rccstatus eq "off" ) && ( $pingstatus eq "off" ) ) {
|
||||||
Log3( $hash, 5, "SB_SERVER_Alive($name): ping timeout. " .
|
if( ReadingsVal( $name, "power", "on" ) eq "on" ) {
|
||||||
|
# the first time we realize the server is off
|
||||||
|
Log3( $hash, 5, "SB_SERVER_Alive($name): " .
|
||||||
"SB-Server in hibernate / suspend?." );
|
"SB-Server in hibernate / suspend?." );
|
||||||
|
|
||||||
if( ReadingsVal( $name, "power", "off" ) eq "on" ) {
|
|
||||||
# first time we realized server is away
|
# first time we realized server is away
|
||||||
|
$hash->{CLICONNECTION} = "off";
|
||||||
readingsSingleUpdate( $hash, "power", "off", 1 );
|
readingsSingleUpdate( $hash, "power", "off", 1 );
|
||||||
readingsSingleUpdate( $hash, "alivecheck", "?", 0 );
|
$hash->{ALIVECHECK} = "?";
|
||||||
|
|
||||||
# signal that to our clients
|
# signal that to our clients
|
||||||
SB_SERVER_Broadcast( $hash, "SERVER", "OFF" );
|
SB_SERVER_Broadcast( $hash, "SERVER", "OFF" );
|
||||||
@ -806,14 +858,14 @@ sub SB_SERVER_Alive( $ ) {
|
|||||||
# remove all timers we created
|
# remove all timers we created
|
||||||
RemoveInternalTimer( $hash );
|
RemoveInternalTimer( $hash );
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
# we shouldn't end up here
|
||||||
|
Log3( $hash, 5, "SB_SERVER_Alive($name): funny server status " .
|
||||||
|
"received. Ping=" . $pingstatus . " RCC=" . $rccstatus );
|
||||||
}
|
}
|
||||||
|
|
||||||
# close our ping mechanism again
|
|
||||||
$p->close( );
|
|
||||||
|
|
||||||
# do an update of the status
|
# do an update of the status
|
||||||
InternalTimer( gettimeofday() + AttrVal( $name, "alivetimer", 120 ),
|
InternalTimer( $nexttime,
|
||||||
"SB_SERVER_Alive",
|
"SB_SERVER_Alive",
|
||||||
$hash,
|
$hash,
|
||||||
0 );
|
0 );
|
||||||
@ -937,6 +989,13 @@ sub SB_SERVER_ParseServerStatus( $$ ) {
|
|||||||
} elsif( $_ =~ /^(scanning:)([0-9]*)/ ) {
|
} elsif( $_ =~ /^(scanning:)([0-9]*)/ ) {
|
||||||
readingsBulkUpdate( $hash, "scanning", $2 );
|
readingsBulkUpdate( $hash, "scanning", $2 );
|
||||||
next;
|
next;
|
||||||
|
} elsif( $_ =~ /^(rescan:)([0-9]*)/ ) {
|
||||||
|
if( $2 eq "1" ) {
|
||||||
|
readingsBulkUpdate( $hash, "scanning", "yes" );
|
||||||
|
} else {
|
||||||
|
readingsBulkUpdate( $hash, "scanning", "no" );
|
||||||
|
}
|
||||||
|
next;
|
||||||
} elsif( $_ =~ /^(version:)([0-9\.]*)/ ) {
|
} elsif( $_ =~ /^(version:)([0-9\.]*)/ ) {
|
||||||
readingsBulkUpdate( $hash, "serverversion", $2 );
|
readingsBulkUpdate( $hash, "serverversion", $2 );
|
||||||
next;
|
next;
|
||||||
@ -1080,6 +1139,20 @@ sub SB_SERVER_ParseServerStatus( $$ ) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# the list for the sync masters
|
||||||
|
# make all client create e new sync master list
|
||||||
|
SB_SERVER_Broadcast( $hash, "SYNCMASTER",
|
||||||
|
"FLUSH dont care", undef );
|
||||||
|
|
||||||
|
# now send the list for the sync masters
|
||||||
|
foreach my $player ( keys %players ) {
|
||||||
|
my $uniqueid = join( "", split( ":", $players{$player}{MAC} ) );
|
||||||
|
SB_SERVER_Broadcast( $hash, "SYNCMASTER",
|
||||||
|
"ADD $players{$player}{name} " .
|
||||||
|
"$players{$player}{MAC} $uniqueid", undef );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1345,6 +1418,83 @@ sub SB_SERVER_CMDStackPop( $ ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# parse the list of known Playlists
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
sub SB_SERVER_ParseServerPlaylists( $$ ) {
|
||||||
|
my( $hash, $dataptr ) = @_;
|
||||||
|
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
|
||||||
|
my $namebuf = "";
|
||||||
|
my $uniquename = "";
|
||||||
|
my $idbuf = -1;
|
||||||
|
|
||||||
|
# typically the start index being a number
|
||||||
|
if( $dataptr->[ 0 ] =~ /^([0-9])*/ ) {
|
||||||
|
shift( @{$dataptr} );
|
||||||
|
} else {
|
||||||
|
Log3( $hash, 5, "SB_SERVER_ParseServerPlaylists($name): entry is " .
|
||||||
|
"not the start number" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# typically the max index being a number
|
||||||
|
if( $dataptr->[ 0 ] =~ /^([0-9])*/ ) {
|
||||||
|
shift( @{$dataptr} );
|
||||||
|
} else {
|
||||||
|
Log3( $hash, 5, "SB_SERVER_ParseServerPlaylists($name): entry is " .
|
||||||
|
"not the end number" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $datastr = join( " ", @{$dataptr} );
|
||||||
|
|
||||||
|
Log3( $hash, 5, "SB_SERVER_ParseServerPlaylists($name): data to parse: " .
|
||||||
|
$datastr );
|
||||||
|
|
||||||
|
# make all client create e new favorites list
|
||||||
|
SB_SERVER_Broadcast( $hash, "PLAYLISTS",
|
||||||
|
"FLUSH dont care", undef );
|
||||||
|
|
||||||
|
my @data1 = split( " ", $datastr );
|
||||||
|
|
||||||
|
foreach( @data1 ) {
|
||||||
|
if( $_ =~ /^(id:)(.*)/ ) {
|
||||||
|
Log3( $hash, 5, "SB_SERVER_ParseServerPlaylists($name): " .
|
||||||
|
"id:$idbuf name:$namebuf " );
|
||||||
|
if( $idbuf != -1 ) {
|
||||||
|
$uniquename = SB_SERVER_FavoritesName2UID( $namebuf );
|
||||||
|
SB_SERVER_Broadcast( $hash, "PLAYLISTS",
|
||||||
|
"ADD $namebuf $idbuf $uniquename", undef );
|
||||||
|
}
|
||||||
|
$idbuf = $2;
|
||||||
|
$namebuf = "";
|
||||||
|
$uniquename = "";
|
||||||
|
next;
|
||||||
|
} elsif( $_ =~ /^(playlist:)(.*)/ ) {
|
||||||
|
$namebuf = $2;
|
||||||
|
next;
|
||||||
|
} elsif( $_ =~ /^(count:)([0-9]*)/ ) {
|
||||||
|
# the last entry of the return
|
||||||
|
Log3( $hash, 5, "SB_SERVER_ParseServerPlaylists($name): " .
|
||||||
|
"id:$idbuf name:$namebuf " );
|
||||||
|
if( $idbuf != -1 ) {
|
||||||
|
$uniquename = SB_SERVER_FavoritesName2UID( $namebuf );
|
||||||
|
SB_SERVER_Broadcast( $hash, "PLAYLISTS",
|
||||||
|
"ADD $namebuf $idbuf $uniquename", undef );
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$namebuf .= "_" . $_;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
||||||
=pod
|
=pod
|
||||||
|
Loading…
x
Reference in New Issue
Block a user