@@ -2,28 +2,29 @@
package org .dolphinemu .dolphinemu .services ;
import android .app .IntentService ;
import android .content .Context ;
import android .content .Intent ;
import androidx .localbroadcastmanager .content .LocalBroadcastManager ;
import org .dolphinemu .dolphinemu .DolphinApplication ;
import org .dolphinemu .dolphinemu .model .GameFile ;
import org .dolphinemu .dolphinemu .model .GameFileCache ;
import org .dolphinemu .dolphinemu .ui .platform .Platform ;
import org .dolphinemu .dolphinemu .utils .AfterDirectoryInitializationRunner ;
import java .io .File ;
import java .util .ArrayList ;
import java .util .Arrays ;
import java .util .List ;
import java .util .concurrent .atomic .AtomicInteger ;
import java .util .concurrent .ExecutorService ;
import java .util .concurrent .Executors ;
import java .util .concurrent .atomic .AtomicBoolean ;
import java .util .concurrent .atomic .AtomicReference ;
/**
* A service that loads game list data on a separate thread.
* Loads game list data on a separate thread.
*/
public final class GameFileCacheService extends IntentService
public final class GameFileCacheManager
{
/**
* This is broadcast when the contents of the cache change.
@@ -37,19 +38,16 @@ public final class GameFileCacheService extends IntentService
public static final String DONE_LOADING =
"org.dolphinemu.dolphinemu.GAME_FILE_CACHE_DONE_LOADING" ;
private static final String ACTION_LOAD = "org.dolphinemu.dolphinemu.LOAD_GAME_FILE_CACHE" ;
private static final String ACTION_RESCAN = "org.dolphinemu.dolphinemu.RESCAN_GAME_FILE_CACHE" ;
private static GameFileCache gameFileCache = null ;
private static final AtomicReference <GameFile []> gameFiles =
new AtomicReference <>(new GameFile []{});
private static final AtomicInteger unhandledIntents = new AtomicInteger (0 );
private static final AtomicInteger unhandledRescanIntents = new AtomicInteger (0 );
public GameFileCacheService ()
private static final ExecutorService executor = Executors .newFixedThreadPool (1 );
private static final AtomicBoolean loadInProgress = new AtomicBoolean (false );
private static final AtomicBoolean rescanInProgress = new AtomicBoolean (false );
private GameFileCacheManager ()
{
// Superclass constructor is called to name the thread on which this service executes.
super ("GameFileCacheService" );
}
public static List <GameFile > getGameFilesForPlatform (Platform platform )
@@ -113,34 +111,29 @@ public static String[] findSecondDiscAndGetPaths(GameFile gameFile)
*/
public static boolean isLoading ()
{
return unhandledIntents .get () != 0 ;
return loadInProgress .get ();
}
/**
* Returns true if in the process of rescanning.
*/
public static boolean isRescanning ()
{
return unhandledRescanIntents .get () != 0 ;
}
private static void startService (Context context , String action )
{
Intent intent = new Intent (context , GameFileCacheService .class );
intent .setAction (action );
context .startService (intent );
return rescanInProgress .get ();
}
/**
* Asynchronously loads the game file cache from disk without checking
* which games are present on the file system.
* Asynchronously loads the game file cache from disk, without checking
* if the games are still present in the user's configured folders.
* If this has already been called, calling it again has no effect.
*/
public static void startLoad (Context context )
{
unhandledIntents .getAndIncrement ();
new AfterDirectoryInitializationRunner ().run (context , false ,
() -> startService (context , ACTION_LOAD ));
if (loadInProgress .compareAndSet (false , true ))
{
new AfterDirectoryInitializationRunner ().run (context , false ,
() -> executor .execute (GameFileCacheManager ::load ));
}
}
/**
@@ -150,11 +143,11 @@ public static void startLoad(Context context)
*/
public static void startRescan (Context context )
{
unhandledIntents . getAndIncrement ();
unhandledRescanIntents . getAndIncrement ();
new AfterDirectoryInitializationRunner (). run ( context , false ,
() -> startService ( context , ACTION_RESCAN ));
if ( rescanInProgress . compareAndSet ( false , true ))
{
new AfterDirectoryInitializationRunner (). run ( context , false ,
() -> executor . execute ( GameFileCacheManager :: rescan ));
}
}
public static GameFile addOrGet (String gamePath )
@@ -179,11 +172,14 @@ public static GameFile addOrGet(String gamePath)
}
}
@ Override
protected void onHandleIntent (Intent intent )
/**
* Loads the game file cache from disk, without checking if the
* games are still present in the user's configured folders.
* If this has already been called, calling it again has no effect.
*/
private static void load ()
{
// Load the game list cache if it isn't already loaded, otherwise do nothing
if (ACTION_LOAD .equals (intent .getAction ()) && gameFileCache == null )
if (gameFileCache == null )
{
GameFileCache temp = new GameFileCache ();
synchronized (temp )
@@ -198,56 +194,61 @@ protected void onHandleIntent(Intent intent)
}
}
// Rescan the file system and update the game list cache with the results
if (ACTION_RESCAN .equals (intent .getAction ()))
{
if (gameFileCache != null )
{
String [] gamePaths = GameFileCache .getAllGamePaths ();
loadInProgress .set (false );
if (!rescanInProgress .get ())
sendBroadcast (DONE_LOADING );
}
boolean changed ;
synchronized ( gameFileCache )
{
changed = gameFileCache . update ( gamePaths );
}
if ( changed )
{
updateGameFileArray ();
sendBroadcast ( CACHE_UPDATED );
}
/**
* Scans for games in the user's configured folders,
* updating the game file cache with the results.
* If load hasn't been called before this, this has no effect.
*/
private static void rescan ( )
{
if ( gameFileCache != null )
{
String [] gamePaths = GameFileCache . getAllGamePaths ();
boolean additionalMetadataChanged = gameFileCache .updateAdditionalMetadata ();
if (additionalMetadataChanged )
{
updateGameFileArray ();
sendBroadcast (CACHE_UPDATED );
}
boolean changed ;
synchronized (gameFileCache )
{
changed = gameFileCache .update (gamePaths );
}
if (changed )
{
updateGameFileArray ();
sendBroadcast (CACHE_UPDATED );
}
if (changed || additionalMetadataChanged )
{
gameFileCache .save ();
}
boolean additionalMetadataChanged = gameFileCache .updateAdditionalMetadata ();
if (additionalMetadataChanged )
{
updateGameFileArray ();
sendBroadcast (CACHE_UPDATED );
}
unhandledRescanIntents .decrementAndGet ();
if (changed || additionalMetadataChanged )
{
gameFileCache .save ();
}
}
int intentsLeft = unhandledIntents .decrementAndGet ();
if (intentsLeft == 0 )
{
rescanInProgress .set (false );
if (!loadInProgress .get ())
sendBroadcast (DONE_LOADING );
}
}
private void updateGameFileArray ()
private static void updateGameFileArray ()
{
GameFile [] gameFilesTemp = gameFileCache .getAllGames ();
Arrays .sort (gameFilesTemp , (lhs , rhs ) -> lhs .getTitle ().compareToIgnoreCase (rhs .getTitle ()));
gameFiles .set (gameFilesTemp );
}
private void sendBroadcast (String action )
private static void sendBroadcast (String action )
{
LocalBroadcastManager .getInstance (this ).sendBroadcast (new Intent (action ));
LocalBroadcastManager .getInstance (DolphinApplication .getAppContext ())
.sendBroadcast (new Intent (action ));
}
}