2016-07-03 17:24:33 +00:00
// Copyright (c) 2014- PPSSPP Project.
// 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, version 2.0 or later versions.
// 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 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
2017-02-27 19:51:36 +00:00
# include <algorithm>
# include <thread>
2016-07-04 00:38:29 +00:00
# include "base/timeutil.h"
# include "ext/vjson/json.h"
2016-07-03 19:38:55 +00:00
# include "file/fd_util.h"
2016-07-04 00:38:29 +00:00
# include "i18n/i18n.h"
2016-07-03 19:38:55 +00:00
# include "net/http_client.h"
2016-07-03 17:24:33 +00:00
# include "net/http_server.h"
# include "net/resolve.h"
# include "net/sinks.h"
# include "thread/threadutil.h"
# include "Common/Common.h"
# include "Common/FileUtil.h"
# include "Core/Config.h"
# include "UI/RemoteISOScreen.h"
using namespace UI ;
2016-07-04 00:38:29 +00:00
static const char * REPORT_HOSTNAME = " report.ppsspp.org " ;
static const int REPORT_PORT = 80 ;
2016-07-03 17:24:33 +00:00
enum class ServerStatus {
STOPPED ,
STARTING ,
RUNNING ,
STOPPING ,
} ;
static std : : thread * serverThread = nullptr ;
static ServerStatus serverStatus ;
2017-02-27 20:57:46 +00:00
static std : : mutex serverStatusLock ;
static std : : condition_variable serverStatusCond ;
2016-07-03 17:24:33 +00:00
2016-07-04 14:32:49 +00:00
static bool scanCancelled = false ;
2017-03-22 07:00:52 +00:00
static bool scanAborted = false ;
2016-07-04 14:32:49 +00:00
2016-07-03 17:24:33 +00:00
static void UpdateStatus ( ServerStatus s ) {
2017-02-27 20:57:46 +00:00
std : : lock_guard < std : : mutex > guard ( serverStatusLock ) ;
2016-07-03 17:24:33 +00:00
serverStatus = s ;
serverStatusCond . notify_one ( ) ;
}
static ServerStatus RetrieveStatus ( ) {
2017-02-27 20:57:46 +00:00
std : : lock_guard < std : : mutex > guard ( serverStatusLock ) ;
2016-07-03 17:24:33 +00:00
return serverStatus ;
}
2016-07-03 19:38:55 +00:00
// This reports the local IP address to report.ppsspp.org, which can then
// relay that address to a mobile device searching for the server.
static void RegisterServer ( int port ) {
http : : Client http ;
Buffer theVoid ;
2016-07-04 00:38:29 +00:00
if ( http . Resolve ( REPORT_HOSTNAME , REPORT_PORT ) ) {
2017-03-22 07:00:52 +00:00
if ( http . Connect ( 2 , 20.0 , & scanCancelled ) ) {
2016-07-04 14:34:40 +00:00
char resource [ 1024 ] = { } ;
std : : string ip = fd_util : : GetLocalIP ( http . sock ( ) ) ;
snprintf ( resource , sizeof ( resource ) - 1 , " /match/update?local=%s&port=%d " , ip . c_str ( ) , port ) ;
2016-07-03 19:38:55 +00:00
2016-07-04 14:34:40 +00:00
http . GET ( resource , & theVoid ) ;
http . Disconnect ( ) ;
}
2016-07-03 19:38:55 +00:00
}
}
2016-07-03 17:24:33 +00:00
static void ExecuteServer ( ) {
setCurrentThreadName ( " HTTPServer " ) ;
auto http = new http : : Server ( new threading : : SameThreadExecutor ( ) ) ;
2016-07-03 18:32:18 +00:00
std : : map < std : : string , std : : string > paths ;
for ( std : : string filename : g_Config . recentIsos ) {
# ifdef _WIN32
static const std : : string sep = " \\ / " ;
# else
static const std : : string sep = " / " ;
# endif
size_t basepos = filename . find_last_of ( sep ) ;
std : : string basename = " / " + ( basepos = = filename . npos ? filename : filename . substr ( basepos + 1 ) ) ;
// Let's not serve directories, since they won't work. Only single files.
// Maybe can do PBPs and other files later. Would be neat to stream virtual disc filesystems.
if ( endsWithNoCase ( basename , " .cso " ) | | endsWithNoCase ( basename , " .iso " ) ) {
2016-07-04 03:44:39 +00:00
paths [ ReplaceAll ( basename , " " , " %20 " ) ] = filename ;
2016-07-03 18:32:18 +00:00
}
}
auto handler = [ & ] ( const http : : Request & request ) {
std : : string filename = paths [ request . resource ( ) ] ;
s64 sz = File : : GetFileSize ( filename ) ;
std : : string range ;
if ( request . Method ( ) = = http : : RequestHeader : : HEAD ) {
request . WriteHttpResponseHeader ( 200 , sz , " application/octet-stream " , " Accept-Ranges: bytes \r \n " ) ;
} else if ( request . GetHeader ( " range " , & range ) ) {
s64 begin = 0 , last = 0 ;
if ( sscanf ( range . c_str ( ) , " bytes=%lld-%lld " , & begin , & last ) ! = 2 ) {
request . WriteHttpResponseHeader ( 400 , - 1 , " text/plain " ) ;
request . Out ( ) - > Push ( " Could not understand range request. " ) ;
return ;
}
if ( begin < 0 | | begin > last | | last > = sz ) {
request . WriteHttpResponseHeader ( 416 , - 1 , " text/plain " ) ;
request . Out ( ) - > Push ( " Range goes outside of file. " ) ;
return ;
}
FILE * fp = File : : OpenCFile ( filename , " rb " ) ;
if ( ! fp | | fseek ( fp , begin , SEEK_SET ) ! = 0 ) {
request . WriteHttpResponseHeader ( 500 , - 1 , " text/plain " ) ;
request . Out ( ) - > Push ( " File access failed. " ) ;
if ( fp ) {
fclose ( fp ) ;
}
return ;
}
s64 len = last - begin + 1 ;
char contentRange [ 1024 ] ;
sprintf ( contentRange , " Content-Range: bytes %lld-%lld/%lld \r \n " , begin , last , sz ) ;
request . WriteHttpResponseHeader ( 206 , len , " application/octet-stream " , contentRange ) ;
const size_t CHUNK_SIZE = 16 * 1024 ;
char * buf = new char [ CHUNK_SIZE ] ;
for ( s64 pos = 0 ; pos < len ; pos + = CHUNK_SIZE ) {
s64 chunklen = std : : min ( len - pos , ( s64 ) CHUNK_SIZE ) ;
fread ( buf , chunklen , 1 , fp ) ;
request . Out ( ) - > Push ( buf , chunklen ) ;
}
fclose ( fp ) ;
delete [ ] buf ;
request . Out ( ) - > Flush ( ) ;
} else {
request . WriteHttpResponseHeader ( 418 , - 1 , " text/plain " ) ;
request . Out ( ) - > Push ( " This server only supports range requests. " ) ;
}
} ;
for ( auto pair : paths ) {
http - > RegisterHandler ( pair . first . c_str ( ) , handler ) ;
}
2016-07-04 04:09:17 +00:00
if ( ! http - > Listen ( g_Config . iRemoteISOPort ) ) {
2016-07-04 14:24:04 +00:00
if ( ! http - > Listen ( 0 ) ) {
2017-03-06 12:10:23 +00:00
ERROR_LOG ( FILESYS , " Unable to listen on any port " ) ;
2016-07-04 14:24:04 +00:00
UpdateStatus ( ServerStatus : : STOPPED ) ;
return ;
}
2016-07-04 04:09:17 +00:00
}
2016-07-03 17:24:33 +00:00
UpdateStatus ( ServerStatus : : RUNNING ) ;
2016-07-04 04:09:17 +00:00
g_Config . iRemoteISOPort = http - > Port ( ) ;
2016-07-03 19:38:55 +00:00
RegisterServer ( http - > Port ( ) ) ;
2016-07-04 00:38:29 +00:00
double lastRegister = real_time_now ( ) ;
2016-07-03 17:24:33 +00:00
while ( RetrieveStatus ( ) = = ServerStatus : : RUNNING ) {
2016-07-03 18:32:18 +00:00
http - > RunSlice ( 5.0 ) ;
2016-07-04 00:38:29 +00:00
double now = real_time_now ( ) ;
if ( now > lastRegister + 540.0 ) {
RegisterServer ( http - > Port ( ) ) ;
lastRegister = now ;
}
2016-07-03 17:24:33 +00:00
}
2016-07-04 14:24:04 +00:00
http - > Stop ( ) ;
2016-07-03 17:24:33 +00:00
UpdateStatus ( ServerStatus : : STOPPED ) ;
}
2016-07-04 03:48:27 +00:00
static bool FindServer ( std : : string & resultHost , int & resultPort ) {
2016-07-04 00:38:29 +00:00
http : : Client http ;
Buffer result ;
int code = 500 ;
2017-03-22 07:00:52 +00:00
auto TryServer = [ & ] ( const std : : string & host , int port ) {
// Don't wait as long for a connect - we need a good connection for smooth streaming anyway.
// This way if it's down, we'll find the right one faster.
if ( http . Resolve ( host . c_str ( ) , port ) & & http . Connect ( 1 , 10.0 , & scanCancelled ) ) {
http . Disconnect ( ) ;
resultHost = host ;
resultPort = port ;
return true ;
}
return false ;
} ;
2017-02-14 02:18:14 +00:00
// Try last server first, if it is set
2017-03-22 07:00:52 +00:00
if ( g_Config . iLastRemoteISOPort & & g_Config . sLastRemoteISOServer ! = " " ) {
if ( TryServer ( g_Config . sLastRemoteISOServer . c_str ( ) , g_Config . iLastRemoteISOPort ) ) {
return true ;
}
2017-02-14 02:18:14 +00:00
}
2017-03-22 07:06:02 +00:00
// Don't scan if in manual mode.
if ( g_Config . bRemoteISOManual | | scanCancelled ) {
2017-03-06 04:26:53 +00:00
return false ;
}
2016-07-04 00:38:29 +00:00
2017-03-06 04:26:53 +00:00
// Start by requesting a list of recent local ips for this network.
if ( http . Resolve ( REPORT_HOSTNAME , REPORT_PORT ) ) {
2017-03-22 07:00:52 +00:00
if ( http . Connect ( 2 , 20.0 , & scanCancelled ) ) {
2017-03-06 04:26:53 +00:00
code = http . GET ( " /match/list " , & result ) ;
http . Disconnect ( ) ;
2017-02-22 01:45:58 +00:00
}
2017-03-06 04:26:53 +00:00
}
if ( code ! = 200 | | scanCancelled ) {
return false ;
}
2016-07-04 00:38:29 +00:00
2017-03-06 04:26:53 +00:00
std : : string json ;
result . TakeAll ( & json ) ;
2016-07-04 00:38:29 +00:00
2017-03-06 04:26:53 +00:00
JsonReader reader ( json . c_str ( ) , json . size ( ) ) ;
if ( ! reader . ok ( ) ) {
return false ;
}
2016-07-04 00:38:29 +00:00
2017-03-06 04:26:53 +00:00
const json_value * entries = reader . root ( ) ;
if ( ! entries ) {
return false ;
}
2016-07-04 00:38:29 +00:00
2017-03-06 04:26:53 +00:00
std : : vector < std : : string > servers ;
const json_value * entry = entries - > first_child ;
2017-03-22 07:00:52 +00:00
while ( entry & & ! scanCancelled ) {
2017-03-06 04:26:53 +00:00
const char * host = entry - > getString ( " ip " , " " ) ;
int port = entry - > getInt ( " p " , 0 ) ;
char url [ 1024 ] = { } ;
snprintf ( url , sizeof ( url ) , " http://%s:%d " , host , port ) ;
servers . push_back ( url ) ;
2016-07-04 00:38:29 +00:00
2017-03-22 07:00:52 +00:00
if ( TryServer ( host , port ) ) {
2017-03-06 04:26:53 +00:00
return true ;
2016-07-04 00:38:29 +00:00
}
2017-03-06 04:26:53 +00:00
entry = entry - > next_sibling ;
2016-07-04 00:38:29 +00:00
}
2017-03-06 04:26:53 +00:00
2016-07-04 00:38:29 +00:00
// None of the local IPs were reachable.
2016-07-04 03:48:27 +00:00
return false ;
}
static bool LoadGameList ( const std : : string & host , int port , std : : vector < std : : string > & games ) {
http : : Client http ;
Buffer result ;
int code = 500 ;
2017-03-11 05:23:49 +00:00
std : : vector < std : : string > responseHeaders ;
2017-02-22 01:45:58 +00:00
std : : string subdir = " / " ;
2017-03-10 04:51:44 +00:00
size_t offset ;
2017-02-22 01:45:58 +00:00
if ( g_Config . bRemoteISOManual ) {
subdir = g_Config . sRemoteISOSubdir ;
2017-03-10 04:51:44 +00:00
offset = subdir . find_last_of ( " / " ) ;
2017-03-11 06:53:16 +00:00
if ( offset ! = subdir . length ( ) - 1 & & offset ! = subdir . npos ) {
2017-03-10 04:51:44 +00:00
//truncate everything after last /
subdir . erase ( offset + 1 ) ;
}
2017-02-22 01:45:58 +00:00
}
2016-07-04 03:48:27 +00:00
2017-02-22 01:45:58 +00:00
// Start by requesting the list of games from the server.
2016-07-04 03:48:27 +00:00
if ( http . Resolve ( host . c_str ( ) , port ) ) {
2017-03-22 07:00:52 +00:00
if ( http . Connect ( 2 , 20.0 , & scanCancelled ) ) {
2017-03-11 06:53:16 +00:00
code = http . GET ( subdir . c_str ( ) , & result , responseHeaders ) ;
2016-07-04 14:34:40 +00:00
http . Disconnect ( ) ;
}
2016-07-04 03:48:27 +00:00
}
2016-07-04 14:32:49 +00:00
if ( code ! = 200 | | scanCancelled ) {
2016-07-04 03:48:27 +00:00
return false ;
}
std : : string listing ;
std : : vector < std : : string > items ;
result . TakeAll ( & listing ) ;
2017-03-12 06:15:40 +00:00
std : : string contentType ;
for ( const std : : string & header : responseHeaders ) {
if ( startsWithNoCase ( header , " Content-Type: " ) ) {
contentType = header . substr ( strlen ( " Content-Type: " ) ) ;
// Strip any whitespace (TODO: maybe move this to stringutil?)
contentType . erase ( 0 , contentType . find_first_not_of ( " \t \r \n " ) ) ;
contentType . erase ( contentType . find_last_not_of ( " \t \r \n " ) + 1 ) ;
}
}
// TODO: Technically, "TExt/hTml ; chaRSet = Utf8" should pass, but "text/htmlese" should not.
// But unlikely that'll be an issue.
bool parseHtml = startsWithNoCase ( contentType , " text/html " ) ;
bool parseText = startsWithNoCase ( contentType , " text/plain " ) ;
if ( parseText ) {
2017-03-11 05:23:49 +00:00
//ppsspp server
SplitString ( listing , ' \n ' , items ) ;
for ( const std : : string & item : items ) {
if ( ! endsWithNoCase ( item , " .cso " ) & & ! endsWithNoCase ( item , " .iso " ) & & ! endsWithNoCase ( item , " .pbp " ) ) {
continue ;
}
char temp [ 1024 ] = { } ;
snprintf ( temp , sizeof ( temp ) - 1 , " http://%s:%d%s " , host . c_str ( ) , port , item . c_str ( ) ) ;
games . push_back ( temp ) ;
2016-07-04 03:48:27 +00:00
}
2017-03-12 06:15:40 +00:00
} else if ( parseHtml ) {
2017-03-11 05:23:49 +00:00
//other webserver
2017-03-06 04:26:53 +00:00
GetQuotedStrings ( listing , items ) ;
2017-03-11 05:23:49 +00:00
for ( const std : : string & item : items ) {
if ( ! endsWithNoCase ( item , " .cso " ) & & ! endsWithNoCase ( item , " .iso " ) & & ! endsWithNoCase ( item , " .pbp " ) ) {
continue ;
}
2016-07-04 03:48:27 +00:00
2017-03-11 05:23:49 +00:00
char temp [ 1024 ] = { } ;
2017-02-22 01:45:58 +00:00
snprintf ( temp , sizeof ( temp ) - 1 , " http://%s:%d%s%s " , host . c_str ( ) , port , subdir . c_str ( ) , item . c_str ( ) ) ;
2017-03-11 05:23:49 +00:00
games . push_back ( temp ) ;
}
2017-03-12 06:15:40 +00:00
} else {
ERROR_LOG ( FILESYS , " Unsupported Content-Type: %s " , contentType . c_str ( ) ) ;
return false ;
2016-07-04 03:48:27 +00:00
}
2017-03-12 06:15:40 +00:00
2017-02-22 01:45:58 +00:00
//save for next time unless manual is true
if ( ! games . empty ( ) & & ! g_Config . bRemoteISOManual ) {
2017-02-14 02:18:14 +00:00
g_Config . sLastRemoteISOServer = host ;
g_Config . iLastRemoteISOPort = port ;
}
2016-07-04 03:48:27 +00:00
return ! games . empty ( ) ;
2016-07-04 00:38:29 +00:00
}
2016-07-03 18:41:27 +00:00
RemoteISOScreen : : RemoteISOScreen ( ) : serverRunning_ ( false ) , serverStopping_ ( false ) {
2016-07-03 17:43:35 +00:00
}
2017-03-15 05:01:18 +00:00
void RemoteISOScreen : : update ( ) {
UIScreenWithBackground : : update ( ) ;
2016-07-03 17:43:35 +00:00
bool nowRunning = RetrieveStatus ( ) ! = ServerStatus : : STOPPED ;
2016-07-03 18:41:27 +00:00
if ( serverStopping_ & & ! nowRunning ) {
2016-07-03 17:43:35 +00:00
// Server stopped, delete the thread.
delete serverThread ;
serverThread = nullptr ;
2016-07-03 18:41:27 +00:00
serverStopping_ = false ;
2016-07-03 17:43:35 +00:00
}
2016-07-03 18:41:27 +00:00
2016-07-03 17:43:35 +00:00
if ( serverRunning_ ! = nowRunning ) {
RecreateViews ( ) ;
}
serverRunning_ = nowRunning ;
2016-07-03 17:24:33 +00:00
}
void RemoteISOScreen : : CreateViews ( ) {
I18NCategory * di = GetI18NCategory ( " Dialog " ) ;
I18NCategory * sy = GetI18NCategory ( " System " ) ;
Margins actionMenuMargins ( 0 , 20 , 15 , 0 ) ;
Margins contentMargins ( 0 , 20 , 5 , 5 ) ;
2017-03-22 07:03:45 +00:00
ViewGroup * leftColumn = new ScrollView ( ORIENT_VERTICAL , new LinearLayoutParams ( FILL_PARENT , FILL_PARENT , 0.4f , contentMargins ) ) ;
2016-07-03 17:24:33 +00:00
LinearLayout * leftColumnItems = new LinearLayout ( ORIENT_VERTICAL , new LayoutParams ( WRAP_CONTENT , FILL_PARENT ) ) ;
ViewGroup * rightColumn = new ScrollView ( ORIENT_VERTICAL , new LinearLayoutParams ( 300 , FILL_PARENT , actionMenuMargins ) ) ;
LinearLayout * rightColumnItems = new LinearLayout ( ORIENT_VERTICAL ) ;
2016-07-03 18:36:23 +00:00
leftColumnItems - > Add ( new TextView ( sy - > T ( " RemoteISODesc " , " Games in your recent list will be shared " ) , new LinearLayoutParams ( Margins ( 12 , 5 , 0 , 5 ) ) ) ) ;
2016-07-03 17:24:33 +00:00
leftColumnItems - > Add ( new TextView ( sy - > T ( " RemoteISOWifi " , " Note: Connect both devices to the same wifi " ) , new LinearLayoutParams ( Margins ( 12 , 5 , 0 , 5 ) ) ) ) ;
rightColumnItems - > SetSpacing ( 0.0f ) ;
2016-07-04 03:46:06 +00:00
Choice * browseChoice = new Choice ( sy - > T ( " Browse Games " ) ) ;
rightColumnItems - > Add ( browseChoice ) - > OnClick . Handle ( this , & RemoteISOScreen : : HandleBrowse ) ;
2016-07-03 18:41:27 +00:00
ServerStatus status = RetrieveStatus ( ) ;
if ( status = = ServerStatus : : STOPPING ) {
2016-07-03 18:41:56 +00:00
rightColumnItems - > Add ( new Choice ( sy - > T ( " Stopping.. " ) ) ) - > SetDisabledPtr ( & serverStopping_ ) ;
2016-07-04 03:46:06 +00:00
browseChoice - > SetEnabled ( false ) ;
2016-07-03 18:41:27 +00:00
} else if ( status ! = ServerStatus : : STOPPED ) {
2016-07-03 18:41:56 +00:00
rightColumnItems - > Add ( new Choice ( sy - > T ( " Stop Sharing " ) ) ) - > OnClick . Handle ( this , & RemoteISOScreen : : HandleStopServer ) ;
2016-07-04 03:46:06 +00:00
browseChoice - > SetEnabled ( false ) ;
2016-07-03 17:24:33 +00:00
} else {
2016-07-03 18:41:56 +00:00
rightColumnItems - > Add ( new Choice ( sy - > T ( " Share Games (Server) " ) ) ) - > OnClick . Handle ( this , & RemoteISOScreen : : HandleStartServer ) ;
2016-07-04 03:46:06 +00:00
browseChoice - > SetEnabled ( true ) ;
2016-07-03 17:24:33 +00:00
}
2017-03-11 04:05:56 +00:00
Choice * settingsChoice = new Choice ( sy - > T ( " Settings " ) ) ;
rightColumnItems - > Add ( settingsChoice ) - > OnClick . Handle ( this , & RemoteISOScreen : : HandleSettings ) ;
2016-07-03 17:24:33 +00:00
2017-03-22 07:03:45 +00:00
LinearLayout * beforeBack = new LinearLayout ( ORIENT_HORIZONTAL , new AnchorLayoutParams ( FILL_PARENT , FILL_PARENT ) ) ;
beforeBack - > Add ( leftColumn ) ;
beforeBack - > Add ( rightColumn ) ;
root_ = new AnchorLayout ( new LayoutParams ( FILL_PARENT , FILL_PARENT ) ) ;
root_ - > Add ( beforeBack ) ;
root_ - > Add ( new Choice ( di - > T ( " Back " ) , " " , false , new AnchorLayoutParams ( 150 , WRAP_CONTENT , 10 , NONE , NONE , 10 ) ) ) - > OnClick . Handle < UIScreen > ( this , & UIScreen : : OnBack ) ;
2016-07-03 17:24:33 +00:00
leftColumn - > Add ( leftColumnItems ) ;
rightColumn - > Add ( rightColumnItems ) ;
}
UI : : EventReturn RemoteISOScreen : : HandleStartServer ( UI : : EventParams & e ) {
2017-02-27 20:57:46 +00:00
std : : lock_guard < std : : mutex > guard ( serverStatusLock ) ;
2016-07-03 17:24:33 +00:00
if ( serverStatus ! = ServerStatus : : STOPPED ) {
return EVENT_SKIPPED ;
}
serverStatus = ServerStatus : : STARTING ;
serverThread = new std : : thread ( & ExecuteServer ) ;
serverThread - > detach ( ) ;
return EVENT_DONE ;
}
UI : : EventReturn RemoteISOScreen : : HandleStopServer ( UI : : EventParams & e ) {
2017-02-27 20:57:46 +00:00
std : : lock_guard < std : : mutex > guard ( serverStatusLock ) ;
2016-07-03 17:24:33 +00:00
if ( serverStatus ! = ServerStatus : : RUNNING ) {
return EVENT_SKIPPED ;
}
serverStatus = ServerStatus : : STOPPING ;
2016-07-03 18:41:27 +00:00
serverStopping_ = true ;
RecreateViews ( ) ;
2016-07-03 17:24:33 +00:00
return EVENT_DONE ;
}
2016-07-04 00:38:29 +00:00
UI : : EventReturn RemoteISOScreen : : HandleBrowse ( UI : : EventParams & e ) {
screenManager ( ) - > push ( new RemoteISOConnectScreen ( ) ) ;
return EVENT_DONE ;
}
2017-03-11 04:05:56 +00:00
UI : : EventReturn RemoteISOScreen : : HandleSettings ( UI : : EventParams & e ) {
screenManager ( ) - > push ( new RemoteISOSettingsScreen ( ) ) ;
return EVENT_DONE ;
}
2016-07-04 03:48:27 +00:00
RemoteISOConnectScreen : : RemoteISOConnectScreen ( ) : status_ ( ScanStatus : : SCANNING ) , nextRetry_ ( 0.0 ) {
2016-07-04 14:32:49 +00:00
scanCancelled = false ;
2017-03-22 07:00:52 +00:00
scanAborted = false ;
2016-07-04 00:38:29 +00:00
scanThread_ = new std : : thread ( [ ] ( RemoteISOConnectScreen * thiz ) {
thiz - > ExecuteScan ( ) ;
} , this ) ;
scanThread_ - > detach ( ) ;
}
RemoteISOConnectScreen : : ~ RemoteISOConnectScreen ( ) {
2016-07-04 14:32:49 +00:00
int maxWait = 5000 ;
scanCancelled = true ;
2016-07-04 03:48:27 +00:00
while ( GetStatus ( ) = = ScanStatus : : SCANNING | | GetStatus ( ) = = ScanStatus : : LOADING ) {
2016-07-04 00:38:29 +00:00
sleep_ms ( 1 ) ;
2016-07-04 14:32:49 +00:00
if ( - - maxWait < 0 ) {
// If it does ever wake up, it may crash... but better than hanging?
2017-03-22 07:00:52 +00:00
scanAborted = true ;
2016-07-04 14:32:49 +00:00
break ;
}
2016-07-04 00:38:29 +00:00
}
delete scanThread_ ;
}
void RemoteISOConnectScreen : : CreateViews ( ) {
I18NCategory * sy = GetI18NCategory ( " System " ) ;
Margins actionMenuMargins ( 0 , 20 , 15 , 0 ) ;
Margins contentMargins ( 0 , 20 , 5 , 5 ) ;
ViewGroup * leftColumn = new ScrollView ( ORIENT_VERTICAL , new LinearLayoutParams ( WRAP_CONTENT , FILL_PARENT , 0.4f , contentMargins ) ) ;
LinearLayout * leftColumnItems = new LinearLayout ( ORIENT_VERTICAL , new LayoutParams ( WRAP_CONTENT , FILL_PARENT ) ) ;
ViewGroup * rightColumn = new ScrollView ( ORIENT_VERTICAL , new LinearLayoutParams ( 300 , FILL_PARENT , actionMenuMargins ) ) ;
LinearLayout * rightColumnItems = new LinearLayout ( ORIENT_VERTICAL ) ;
2016-07-04 03:48:27 +00:00
statusView_ = leftColumnItems - > Add ( new TextView ( sy - > T ( " RemoteISOScanning " , " Scanning... click Share Games on your desktop " ) , new LinearLayoutParams ( Margins ( 12 , 5 , 0 , 5 ) ) ) ) ;
2016-07-04 00:38:29 +00:00
rightColumnItems - > SetSpacing ( 0.0f ) ;
rightColumnItems - > Add ( new Choice ( sy - > T ( " Cancel " ) , " " , false , new AnchorLayoutParams ( 150 , WRAP_CONTENT , 10 , NONE , NONE , 10 ) ) ) - > OnClick . Handle < UIScreen > ( this , & UIScreen : : OnBack ) ;
root_ = new LinearLayout ( ORIENT_HORIZONTAL , new LinearLayoutParams ( FILL_PARENT , FILL_PARENT , 1.0f ) ) ;
root_ - > Add ( leftColumn ) ;
root_ - > Add ( rightColumn ) ;
leftColumn - > Add ( leftColumnItems ) ;
rightColumn - > Add ( rightColumnItems ) ;
}
2017-03-15 05:01:18 +00:00
void RemoteISOConnectScreen : : update ( ) {
2016-07-04 03:48:27 +00:00
I18NCategory * sy = GetI18NCategory ( " System " ) ;
2017-03-15 05:01:18 +00:00
UIScreenWithBackground : : update ( ) ;
2016-07-04 00:38:29 +00:00
2016-07-04 03:48:27 +00:00
ScanStatus s = GetStatus ( ) ;
switch ( s ) {
case ScanStatus : : SCANNING :
case ScanStatus : : LOADING :
break ;
case ScanStatus : : FOUND :
statusView_ - > SetText ( sy - > T ( " RemoteISOLoading " , " Connected - loading game list " ) ) ;
status_ = ScanStatus : : LOADING ;
// Let's reuse scanThread_.
delete scanThread_ ;
scanThread_ = new std : : thread ( [ ] ( RemoteISOConnectScreen * thiz ) {
thiz - > ExecuteLoad ( ) ;
} , this ) ;
scanThread_ - > detach ( ) ;
break ;
case ScanStatus : : FAILED :
nextRetry_ = real_time_now ( ) + 30.0 ;
status_ = ScanStatus : : RETRY_SCAN ;
break ;
case ScanStatus : : RETRY_SCAN :
if ( nextRetry_ < real_time_now ( ) ) {
status_ = ScanStatus : : SCANNING ;
2016-07-04 00:38:29 +00:00
nextRetry_ = 0.0 ;
delete scanThread_ ;
scanThread_ = new std : : thread ( [ ] ( RemoteISOConnectScreen * thiz ) {
thiz - > ExecuteScan ( ) ;
} , this ) ;
scanThread_ - > detach ( ) ;
}
2016-07-04 03:48:27 +00:00
break ;
case ScanStatus : : LOADED :
2017-03-20 00:43:03 +00:00
TriggerFinish ( DR_OK ) ;
2016-07-04 03:48:27 +00:00
screenManager ( ) - > push ( new RemoteISOBrowseScreen ( games_ ) ) ;
break ;
2016-07-04 00:38:29 +00:00
}
}
void RemoteISOConnectScreen : : ExecuteScan ( ) {
2016-07-04 03:48:27 +00:00
FindServer ( host_ , port_ ) ;
2017-03-22 07:00:52 +00:00
if ( scanAborted ) {
2016-07-04 14:32:49 +00:00
return ;
}
2016-07-04 00:38:29 +00:00
2017-02-27 20:57:46 +00:00
std : : lock_guard < std : : mutex > guard ( statusLock_ ) ;
2016-07-04 03:48:27 +00:00
status_ = host_ . empty ( ) ? ScanStatus : : FAILED : ScanStatus : : FOUND ;
2016-07-04 00:38:29 +00:00
}
2016-07-04 03:48:27 +00:00
ScanStatus RemoteISOConnectScreen : : GetStatus ( ) {
2017-02-27 20:57:46 +00:00
std : : lock_guard < std : : mutex > guard ( statusLock_ ) ;
2016-07-04 03:48:27 +00:00
return status_ ;
2016-07-04 00:38:29 +00:00
}
2016-07-04 03:48:27 +00:00
void RemoteISOConnectScreen : : ExecuteLoad ( ) {
bool result = LoadGameList ( host_ , port_ , games_ ) ;
2017-03-22 07:00:52 +00:00
if ( scanAborted ) {
2016-07-04 14:32:49 +00:00
return ;
}
2016-07-04 03:48:27 +00:00
2017-02-27 20:57:46 +00:00
std : : lock_guard < std : : mutex > guard ( statusLock_ ) ;
2016-07-04 03:48:27 +00:00
status_ = result ? ScanStatus : : LOADED : ScanStatus : : FAILED ;
2016-07-04 00:38:29 +00:00
}
2016-07-04 03:48:27 +00:00
class RemoteGameBrowser : public GameBrowser {
public :
RemoteGameBrowser ( const std : : vector < std : : string > & games , bool allowBrowsing , bool * gridStyle_ , std : : string lastText , std : : string lastLink , int flags = 0 , UI : : LayoutParams * layoutParams = 0 )
: GameBrowser ( " !REMOTE " , allowBrowsing , gridStyle_ , lastText , lastLink , flags , layoutParams ) {
games_ = games ;
Refresh ( ) ;
}
protected :
bool DisplayTopBar ( ) override {
return false ;
}
bool HasSpecialFiles ( std : : vector < std : : string > & filenames ) override ;
std : : vector < std : : string > games_ ;
} ;
bool RemoteGameBrowser : : HasSpecialFiles ( std : : vector < std : : string > & filenames ) {
filenames = games_ ;
return true ;
}
RemoteISOBrowseScreen : : RemoteISOBrowseScreen ( const std : : vector < std : : string > & games ) : games_ ( games ) {
2016-07-04 00:38:29 +00:00
}
void RemoteISOBrowseScreen : : CreateViews ( ) {
2016-07-04 03:48:27 +00:00
bool vertical = UseVerticalLayout ( ) ;
I18NCategory * mm = GetI18NCategory ( " MainMenu " ) ;
I18NCategory * di = GetI18NCategory ( " Dialog " ) ;
Margins actionMenuMargins ( 0 , 10 , 10 , 0 ) ;
TabHolder * leftColumn = new TabHolder ( ORIENT_HORIZONTAL , 64 , new LinearLayoutParams ( FILL_PARENT , WRAP_CONTENT ) ) ;
tabHolder_ = leftColumn ;
tabHolder_ - > SetTag ( " RemoteGames " ) ;
gameBrowsers_ . clear ( ) ;
leftColumn - > SetClip ( true ) ;
ScrollView * scrollRecentGames = new ScrollView ( ORIENT_VERTICAL , new LinearLayoutParams ( FILL_PARENT , WRAP_CONTENT ) ) ;
scrollRecentGames - > SetTag ( " RemoteGamesTab " ) ;
RemoteGameBrowser * tabRemoteGames = new RemoteGameBrowser (
games_ , false , & g_Config . bGridView1 , " " , " " , 0 ,
new LinearLayoutParams ( FILL_PARENT , FILL_PARENT ) ) ;
scrollRecentGames - > Add ( tabRemoteGames ) ;
gameBrowsers_ . push_back ( tabRemoteGames ) ;
leftColumn - > AddTab ( mm - > T ( " Remote Server " ) , scrollRecentGames ) ;
tabRemoteGames - > OnChoice . Handle < MainScreen > ( this , & MainScreen : : OnGameSelectedInstant ) ;
tabRemoteGames - > OnHoldChoice . Handle < MainScreen > ( this , & MainScreen : : OnGameSelected ) ;
tabRemoteGames - > OnHighlight . Handle < MainScreen > ( this , & MainScreen : : OnGameHighlight ) ;
ViewGroup * rightColumn = new ScrollView ( ORIENT_VERTICAL ) ;
LinearLayout * rightColumnItems = new LinearLayout ( ORIENT_VERTICAL , new LinearLayoutParams ( FILL_PARENT , WRAP_CONTENT ) ) ;
rightColumnItems - > SetSpacing ( 0.0f ) ;
rightColumn - > Add ( rightColumnItems ) ;
rightColumnItems - > Add ( new Choice ( di - > T ( " Back " ) , " " , false , new AnchorLayoutParams ( 150 , WRAP_CONTENT , 10 , NONE , NONE , 10 ) ) ) - > OnClick . Handle < UIScreen > ( this , & UIScreen : : OnBack ) ;
if ( vertical ) {
root_ = new LinearLayout ( ORIENT_VERTICAL ) ;
rightColumn - > ReplaceLayoutParams ( new LinearLayoutParams ( FILL_PARENT , WRAP_CONTENT ) ) ;
leftColumn - > ReplaceLayoutParams ( new LinearLayoutParams ( FILL_PARENT , WRAP_CONTENT , 1.0 ) ) ;
root_ - > Add ( rightColumn ) ;
root_ - > Add ( leftColumn ) ;
} else {
root_ = new LinearLayout ( ORIENT_HORIZONTAL ) ;
leftColumn - > ReplaceLayoutParams ( new LinearLayoutParams ( FILL_PARENT , WRAP_CONTENT , 1.0 ) ) ;
rightColumn - > ReplaceLayoutParams ( new LinearLayoutParams ( 300 , FILL_PARENT , actionMenuMargins ) ) ;
root_ - > Add ( leftColumn ) ;
root_ - > Add ( rightColumn ) ;
}
root_ - > SetDefaultFocusView ( tabHolder_ ) ;
upgradeBar_ = 0 ;
2016-07-04 00:38:29 +00:00
}
2017-03-11 04:05:56 +00:00
2017-03-22 07:03:45 +00:00
RemoteISOSettingsScreen : : RemoteISOSettingsScreen ( ) {
serverRunning_ = RetrieveStatus ( ) ! = ServerStatus : : STOPPED ; ;
}
void RemoteISOSettingsScreen : : update ( ) {
UIDialogScreenWithBackground : : update ( ) ;
bool nowRunning = RetrieveStatus ( ) ! = ServerStatus : : STOPPED ;
if ( serverRunning_ ! = nowRunning ) {
RecreateViews ( ) ;
}
serverRunning_ = nowRunning ;
}
2017-03-11 04:05:56 +00:00
void RemoteISOSettingsScreen : : CreateViews ( ) {
I18NCategory * di = GetI18NCategory ( " Dialog " ) ;
I18NCategory * n = GetI18NCategory ( " Networking " ) ;
I18NCategory * ms = GetI18NCategory ( " MainSettings " ) ;
2017-03-22 07:03:45 +00:00
ViewGroup * remoteisoSettingsScroll = new ScrollView ( ORIENT_VERTICAL , new LayoutParams ( FILL_PARENT , FILL_PARENT ) ) ;
remoteisoSettingsScroll - > SetTag ( " RemoteISOSettings " ) ;
2017-03-11 04:05:56 +00:00
LinearLayout * remoteisoSettings = new LinearLayout ( ORIENT_VERTICAL ) ;
remoteisoSettings - > SetSpacing ( 0 ) ;
remoteisoSettingsScroll - > Add ( remoteisoSettings ) ;
remoteisoSettings - > Add ( new ItemHeader ( ms - > T ( " Remote Disc Streaming " ) ) ) ;
remoteisoSettings - > Add ( new CheckBox ( & g_Config . bRemoteISOManual , n - > T ( " Manual Mode Client " , " Manual Mode Client " ) ) ) ;
PopupTextInputChoice * remoteServer = remoteisoSettings - > Add ( new PopupTextInputChoice ( & g_Config . sLastRemoteISOServer , n - > T ( " Remote Server " ) , " " , 255 , screenManager ( ) ) ) ;
remoteServer - > SetEnabledPtr ( & g_Config . bRemoteISOManual ) ;
PopupSliderChoice * remotePort = remoteisoSettings - > Add ( new PopupSliderChoice ( & g_Config . iLastRemoteISOPort , 0 , 65535 , n - > T ( " Remote Port " , " Remote Port " ) , 100 , screenManager ( ) ) ) ;
remotePort - > SetEnabledPtr ( & g_Config . bRemoteISOManual ) ;
PopupTextInputChoice * remoteSubdir = remoteisoSettings - > Add ( new PopupTextInputChoice ( & g_Config . sRemoteISOSubdir , n - > T ( " Remote Subdirectory " ) , " " , 255 , screenManager ( ) ) ) ;
remoteSubdir - > SetEnabledPtr ( & g_Config . bRemoteISOManual ) ;
remoteSubdir - > OnChange . Handle ( this , & RemoteISOSettingsScreen : : OnChangeRemoteISOSubdir ) ;
2017-03-22 07:03:45 +00:00
PopupSliderChoice * portChoice = new PopupSliderChoice ( & g_Config . iRemoteISOPort , 0 , 65535 , n - > T ( " Local Server Port " , " Local Server Port " ) , 100 , screenManager ( ) ) ;
remoteisoSettings - > Add ( portChoice ) ;
portChoice - > SetDisabledPtr ( & serverRunning_ ) ;
2017-03-11 04:05:56 +00:00
remoteisoSettings - > Add ( new Spacer ( 25.0 ) ) ;
root_ = new AnchorLayout ( new LayoutParams ( FILL_PARENT , FILL_PARENT ) ) ;
2017-03-22 07:03:45 +00:00
root_ - > Add ( remoteisoSettingsScroll ) ;
AddStandardBack ( root_ ) ;
2017-03-11 04:05:56 +00:00
}
UI : : EventReturn RemoteISOSettingsScreen : : OnChangeRemoteISOSubdir ( UI : : EventParams & e ) {
//Conform to HTTP standards
2017-03-11 06:53:16 +00:00
ReplaceAll ( g_Config . sRemoteISOSubdir , " " , " %20 " ) ;
ReplaceAll ( g_Config . sRemoteISOSubdir , " \\ " , " / " ) ;
//Make sure it begins with /
2017-03-11 04:05:56 +00:00
if ( g_Config . sRemoteISOSubdir [ 0 ] ! = ' / ' )
g_Config . sRemoteISOSubdir = " / " + g_Config . sRemoteISOSubdir ;
2017-03-11 06:53:16 +00:00
2017-03-11 04:05:56 +00:00
return UI : : EVENT_DONE ;
}