[Android] Implement info file support for Android. Also modified the core manager to adapt to this.

- Added JavaDoc to ModuleWrapper and ConfigFile as well.
- Did some tiny simplifications with ConfigFile.java
- Handle the IOExceptions with the parameterized constructor. The only reason this could fail is fail is if the InputStream suddenly closed for an arbitrary reason.
This commit is contained in:
Lioncash 2013-10-31 01:54:42 -04:00
parent 490f35a6c3
commit 7c07e35f4a
6 changed files with 338 additions and 210 deletions

View File

@ -71,7 +71,7 @@ public final class CoreSelection extends ListActivity {
continue;
}
adapter.add(new ModuleWrapper(this, lib, core_config));
adapter.add(new ModuleWrapper(this, lib));
}
this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
@ -80,7 +80,7 @@ public final class CoreSelection extends ListActivity {
@Override
public void onListItemClick(ListView listView, View view, int position, long id) {
final ModuleWrapper item = adapter.getItem(position);
MainMenuActivity.getInstance().setModule(item.file.getAbsolutePath(), item.getText());
MainMenuActivity.getInstance().setModule(item.getUnderlyingFile().getAbsolutePath(), item.getText());
UserPreferences.updateConfigFile(this);
finish();
}

View File

@ -1,51 +1,158 @@
package com.retroarch.browser;
import java.io.File;
import com.retroarch.browser.preferences.util.ConfigFile;
import java.io.File;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.Log;
final class ModuleWrapper implements IconAdapterItem {
public final File file;
private final ConfigFile config;
/**
* Wrapper class that encapsulates a libretro core
* along with information about said core.
*/
public final class ModuleWrapper implements IconAdapterItem, Comparable<ModuleWrapper>
{
private final File file;
private final String displayName;
private final String coreName;
private final String manufacturer;
private final String systemName;
private final String license;
private final String[] supportedExtensions;
public ModuleWrapper(Context context, File file, ConfigFile config) {
/**
* Constructor
*
* @param context The current {@link Context}.
* @param file The {@link File} instance of the core being wrapped.
*/
public ModuleWrapper(Context context, File file)
{
this.file = file;
this.config = config;
// Attempt to get the core's info file.
// Basically this is dataDir/info/[core name].info
Log.d("ModuleWrapper", "File Name: " + file.getName());
// So first, since the core name will contain platform specific strings at the end of name, we trim this.
final String coreName = file.getName().substring(0, file.getName().lastIndexOf("_android.so"));
// Now get the directory where all of the info files are kept (dataDir/info)
final String infoFileDir = context.getApplicationInfo().dataDir + File.separator + "info";
// Now, based off of the trimmed core name, we can get the core info file.
// and attempt to read it as a config file (since it has the same key-value layout).
final String infoFilePath = infoFileDir + File.separator + coreName + ".info";
final ConfigFile infoFile = new ConfigFile(infoFilePath);
Log.d("ModuleWrapper", "Info file path: " + infoFilePath);
Log.d("ModuleWrapper", "Info file exists: " + new File(infoFilePath).exists());
// Now read info out of the info file. Make them an empty string if the key doesn't exist.
this.displayName = (infoFile.keyExists("display_name")) ? infoFile.getString("display_name") : "";
this.coreName = (infoFile.keyExists("corename")) ? infoFile.getString("corename") : "";
this.systemName = (infoFile.keyExists("systemname")) ? infoFile.getString("systemname") : "";
this.manufacturer = (infoFile.keyExists("manufacturer")) ? infoFile.getString("manufacturer") : "";
this.license = (infoFile.keyExists("license")) ? infoFile.getString("license") : "";
// Getting supported extensions is a little different.
// We need to split at every '|' character, since it is
// the delimiter for a new extension that the core supports.
//
// Cores that don't have multiple extensions supported
// don't contain the '|' delimiter, so we just create a String array with
// a size of 1, and just directly assign the retrieved extensions to it.
final String supportedExts = infoFile.getString("supported_extensions");
if (supportedExts.contains("|"))
{
this.supportedExtensions = supportedExts.split("|");
}
else
{
this.supportedExtensions = new String[1];
this.supportedExtensions[0] = supportedExts;
}
}
/**
* Gets the underlying {@link File} instance for this ModuleWrapper.
*
* @return the underlying {@link File} instance for this ModuleWrapper.
*/
public File getUnderlyingFile()
{
return file;
}
/**
* Gets the display name for this wrapped core.
*
* @return the display name for this wrapped core.
*/
public String getDisplayName()
{
return displayName;
}
/**
* Gets the license that this core is protected under.
*
* @return the license that this core is protected under.
*/
public String getCoreLicense()
{
return license;
}
/**
* Gets the name of the manufacturer of the console that
* this core emulates.
*
* @return the name of the manufacturer of the console that
* this core emulates.
*/
public String getManufacturer()
{
return manufacturer;
}
@Override
public boolean isEnabled() {
public boolean isEnabled()
{
return true;
}
@Override
public String getText() {
String stripped = file.getName().replace(".so", "");
if (config.keyExists(stripped)) {
return config.getString(stripped);
} else
return stripped;
public String getText()
{
return coreName;
}
@Override
public String getSubText() {
String stripped = file.getName().replace(".so", "") + "_system";
if (config.keyExists(stripped)) {
return config.getString(stripped);
} else
return null;
public String getSubText()
{
return systemName;
}
@Override
public int getIconResourceId() {
public int getIconResourceId()
{
return 0;
}
@Override
public Drawable getIconDrawable() {
public Drawable getIconDrawable()
{
return null;
}
@Override
public int compareTo(ModuleWrapper other)
{
if(coreName != null)
return coreName.toLowerCase().compareTo(other.coreName.toLowerCase());
else
throw new NullPointerException("The name of this ModuleWrapper is null");
}
}

View File

@ -1,78 +0,0 @@
package com.retroarch.browser.coremanager;
import java.io.File;
/**
* Represents a list item within the CoreManager fragments.
*/
public final class CoreManagerListItem implements Comparable<CoreManagerListItem>
{
private final String name;
private final String subtitle;
private final String path;
private final File underlyingFile;
/**
* Constructor
*
* @param name The name of the core represented by this CoreManagerListItem.
* @param subtitle The subtitle description of the core represented by this CoreManagerListItem.
* @param path The path to the core represented by this CoreManagerListItem.
*/
public CoreManagerListItem(String name, String subtitle, String path)
{
this.name = name;
this.subtitle = subtitle;
this.path = path;
this.underlyingFile = new File(path);
}
/**
* Gets the name of the core represented by this CoreManagerListItem.
*
* @return the name of the core represented by this CoreManagerListItem.
*/
public String getName()
{
return name;
}
/**
* Gets the subtitle description of the core represented by this CoreManagerListItem.
*
* @return the subtitle description of the core represented by this CoreManagerListItem.
*/
public String getSubtitle()
{
return subtitle;
}
/**
* Gets the path to the core represented by this CoreManagerListItem.
*
* @return the path to the core represented by this CoreManagerListItem.
*/
public String getPath()
{
return path;
}
/**
* Gets the underlying {@link File} instance to the core represented by this CoreManagerListItem.
*
* @return the underlying {@link File} instance to the core represented by this CoreManagerListItem.
*/
public File getUnderlyingFile()
{
return underlyingFile;
}
@Override
public int compareTo(CoreManagerListItem other)
{
if(name != null)
return name.toLowerCase().compareTo(other.getName().toLowerCase());
else
throw new NullPointerException("The name of this CoreManagerListItem is null");
}
}

View File

@ -1,14 +1,12 @@
package com.retroarch.browser.coremanager.fragments;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.retroarch.R;
import com.retroarch.browser.coremanager.CoreManagerListItem;
import com.retroarch.browser.preferences.util.ConfigFile;
import com.retroarch.browser.ModuleWrapper;
import com.retroarch.browser.preferences.util.UserPreferences;
import android.app.AlertDialog;
@ -17,7 +15,6 @@ import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -43,18 +40,7 @@ public final class InstalledCoresFragment extends ListFragment
super.onCreate(savedInstanceState);
// The list of items that will be added to the adapter backing this ListFragment.
final List<CoreManagerListItem> items = new ArrayList<CoreManagerListItem>();
// Initialize the core config for retrieving core names.
ConfigFile coreConfig = new ConfigFile();
try
{
coreConfig.append(getActivity().getAssets().open("libretro_cores.cfg"));
}
catch (IOException ioe)
{
Log.e("InstalledCoresFragment", "Failed to load libretro_cores.cfg from assets.");
}
final List<ModuleWrapper> items = new ArrayList<ModuleWrapper>();
// Check if the device supports NEON.
final String cpuInfo = UserPreferences.readCPUInfo();
@ -91,26 +77,8 @@ public final class InstalledCoresFragment extends ListFragment
continue;
}
// Attempt to get the core name.
String coreName;
String strippedName = libName.replace(".so", "");
if (coreConfig.keyExists(strippedName))
coreName = coreConfig.getString(strippedName);
else
coreName = strippedName;
// Attempt to get the core subtitle.
String subtitle = strippedName + "_system";
if (coreConfig.keyExists(subtitle))
subtitle = coreConfig.getString(subtitle);
else
subtitle = "";
Log.d("InstalledCoresFragment", "Core Name: " + coreName);
Log.d("InstalledCoresFragment", "Core Subtitle: " + subtitle);
// Add it to the list.
items.add(new CoreManagerListItem(coreName, subtitle, lib.getPath()));
items.add(new ModuleWrapper(getActivity(), lib));
}
// Sort the list alphabetically
@ -141,10 +109,10 @@ public final class InstalledCoresFragment extends ListFragment
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id)
{
// Begin building the AlertDialog
final CoreManagerListItem item = adapter.getItem(position);
final ModuleWrapper item = adapter.getItem(position);
final AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
alert.setTitle(R.string.uninstall_core);
alert.setMessage(String.format(getString(R.string.uninstall_core_message), item.getName()));
alert.setMessage(String.format(getString(R.string.uninstall_core_message), item.getText()));
alert.setNegativeButton(R.string.no, null);
alert.setPositiveButton(R.string.yes, new OnClickListener()
{
@ -154,13 +122,13 @@ public final class InstalledCoresFragment extends ListFragment
// Attempt to uninstall the core item.
if (item.getUnderlyingFile().delete())
{
Toast.makeText(getActivity(), String.format(getString(R.string.uninstall_success), item.getName()), Toast.LENGTH_LONG).show();
Toast.makeText(getActivity(), String.format(getString(R.string.uninstall_success), item.getText()), Toast.LENGTH_LONG).show();
adapter.remove(item);
adapter.notifyDataSetChanged();
}
else // Failed to uninstall.
{
Toast.makeText(getActivity(), String.format(getString(R.string.uninstall_failure), item.getName()), Toast.LENGTH_LONG).show();
Toast.makeText(getActivity(), String.format(getString(R.string.uninstall_failure), item.getText()), Toast.LENGTH_LONG).show();
}
}
});
@ -173,11 +141,11 @@ public final class InstalledCoresFragment extends ListFragment
/**
* The {@link ArrayAdapter} that backs this InstalledCoresFragment.
*/
private final class InstalledCoresAdapter extends ArrayAdapter<CoreManagerListItem>
private final class InstalledCoresAdapter extends ArrayAdapter<ModuleWrapper>
{
private final Context context;
private final int resourceId;
private final List<CoreManagerListItem> items;
private final List<ModuleWrapper> items;
/**
* Constructor
@ -186,7 +154,7 @@ public final class InstalledCoresFragment extends ListFragment
* @param resourceId The resource ID for a layout file containing a layout to use when instantiating views.
* @param items The list of items to represent in this adapter.
*/
public InstalledCoresAdapter(Context context, int resourceId, List<CoreManagerListItem> items)
public InstalledCoresAdapter(Context context, int resourceId, List<ModuleWrapper> items)
{
super(context, resourceId, items);
@ -196,7 +164,7 @@ public final class InstalledCoresFragment extends ListFragment
}
@Override
public CoreManagerListItem getItem(int i)
public ModuleWrapper getItem(int i)
{
return items.get(i);
}
@ -210,7 +178,7 @@ public final class InstalledCoresFragment extends ListFragment
convertView = vi.inflate(resourceId, parent, false);
}
final CoreManagerListItem item = items.get(position);
final ModuleWrapper item = items.get(position);
if (item != null)
{
TextView title = (TextView) convertView.findViewById(R.id.CoreManagerListItemTitle);
@ -219,12 +187,12 @@ public final class InstalledCoresFragment extends ListFragment
if (title != null)
{
title.setText(item.getName());
title.setText(item.getText());
}
if (subtitle != null)
{
subtitle.setText(item.getSubtitle());
subtitle.setText(item.getSubText());
}
if (icon != null)

View File

@ -4,16 +4,54 @@ import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.FileInputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
public final class ConfigFile {
private HashMap<String, String> map = new HashMap<String, String>();
import android.util.Log;
public void append(InputStream stream) throws IOException {
/**
* Represents a configuration file that works off of a key-value pair
* in the form [key name] = "[value]".
*/
public final class ConfigFile
{
// Map containing all of the key-value pairs.
private final HashMap<String, String> map = new HashMap<String, String>();
/**
* Constructor
*/
public ConfigFile()
{
}
/**
* Constructor
*
* @param filePath The path to the configuration file to open.
*/
public ConfigFile(String filePath)
{
try
{
open(filePath);
}
catch (IOException ioe)
{
Log.e("ConfigFile", "Stream reading the configuration file was suddenly closed for an unknown reason.");
}
}
/**
* Parses a configuration file from the given stream
* and appends the parsed values to the key-value map.
*
* @param stream The {@link InputStream} containing the configuration file to parse.
*/
public void append(InputStream stream) throws IOException
{
BufferedReader br = new BufferedReader(new InputStreamReader(stream));
String line;
@ -23,18 +61,21 @@ public final class ConfigFile {
br.close();
}
public void open(File file) throws IOException {
/**
* Opens a configuration file given by configPath
* and parses all of its key-value pairs, adding
* them to the key-value map.
*
* @param configPath Path to the configuration file to parse.
*/
public void open(String configPath) throws IOException
{
clear();
append(new FileInputStream(file));
append(new FileInputStream(configPath));
}
public ConfigFile(File file) throws IOException {
open(file);
}
public ConfigFile() {}
private void parseLine(String line) {
private void parseLine(String line)
{
String[] tokens = line.split("=", 2);
if (tokens.length < 2)
return;
@ -54,75 +95,181 @@ public final class ConfigFile {
map.put(key, value);
}
public void clear() {
/**
* Clears the key-value map of all currently set keys and values.
*/
public void clear()
{
map.clear();
}
public void write(File file) throws IOException {
PrintWriter writer = new PrintWriter(file.getAbsolutePath());
/**
* Writes the currently set key-value pairs to
*
* @param path The path to save the
*
* @throws IOException
*/
public void write(String path) throws IOException
{
PrintWriter writer = new PrintWriter(path);
for (Map.Entry<String, String> entry : map.entrySet())
{
writer.println(entry.getKey() + " = \"" + entry.getValue() + "\"");
}
writer.close();
}
public void setString(String key, String value) {
map.put(key, value);
}
public void setBoolean(String key, boolean value) {
map.put(key, Boolean.toString(value));
}
public void setInt(String key, int value) {
map.put(key, Integer.toString(value));
}
public void setDouble(String key, double value) {
map.put(key, Double.toString(value));
}
public void setFloat(String key, float value) {
map.put(key, Float.toString(value));
}
public boolean keyExists(String key) {
/**
* Checks if a key exists in the {@link HashMap}
* backing this ConfigFile instance.
*
* @param key The key to check for.
*
* @return true if the key exists in the HashMap backing
* this ConfigFile; false if it doesn't.
*/
public boolean keyExists(String key)
{
return map.containsKey(key);
}
public String getString(String key) {
/**
* Sets a key to the given String value.
*
* @param key The key to set the String value to.
* @param value The String value to set to the key.
*/
public void setString(String key, String value)
{
map.put(key, value);
}
/**
* Sets a key to the given boolean value.
*
* @param key The key to set the boolean value to.
* @param value The boolean value to set to the key.
*/
public void setBoolean(String key, boolean value)
{
map.put(key, Boolean.toString(value));
}
/**
* Sets a key to the given Integer value.
*
* @param key The key to set the Integer value to.
* @param value The Integer value to set to the key.
*/
public void setInt(String key, int value)
{
map.put(key, Integer.toString(value));
}
/**
* Sets a key to the given double value.
*
* @param key The key to set the double value to.
* @param value The double value to set to the key.
*/
public void setDouble(String key, double value)
{
map.put(key, Double.toString(value));
}
/**
* Sets a key to the given float value.
*
* @param key The key to set the float value to.
* @param value The float value to set to the key.
*/
public void setFloat(String key, float value)
{
map.put(key, Float.toString(value));
}
/**
* Gets the String value associated with the given key.
*
* @param key The key to get the String value from.
*
* @return the String object associated with the given key.
*/
public String getString(String key)
{
String ret = map.get(key);
if (ret != null)
return ret;
else
return null;
}
public int getInt(String key) throws NumberFormatException {
/**
* Gets the Integer value associated with the given key.
*
* @param key The key to get the Integer value from.
*
* @return the Integer value associated with the given key.
*/
public int getInt(String key) throws NumberFormatException
{
String str = getString(key);
if (str != null)
return Integer.parseInt(str);
else
throw new NumberFormatException();
}
public double getDouble(String key) throws NumberFormatException {
/**
* Gets the double value associated with the given key.
*
* @param key The key to get the double value from.
*
* @return the double value associated with the given key.
*/
public double getDouble(String key) throws NumberFormatException
{
String str = getString(key);
if (str != null)
return Double.parseDouble(str);
else
throw new NumberFormatException();
}
public float getFloat(String key) throws NumberFormatException {
/**
* Gets the float value associated with the given key.
*
* @param key The key to get the float value from.
*
* @return the float value associated with the given key.
*/
public float getFloat(String key) throws NumberFormatException
{
String str = getString(key);
if (str != null)
return Float.parseFloat(str);
else
throw new NumberFormatException();
}
public boolean getBoolean(String key) {
/**
* Gets the boolean value associated with the given key.
*
* @param key The key to get the boolean value from.
*
* @return the boolean value associated with the given key.
*/
public boolean getBoolean(String key)
{
String str = getString(key);
return Boolean.parseBoolean(str);
}
}

View File

@ -104,15 +104,7 @@ public final class UserPreferences
public static void readbackConfigFile(Context ctx)
{
String path = getDefaultConfigPath(ctx);
ConfigFile config;
try
{
config = new ConfigFile(new File(path));
}
catch (IOException e)
{
return;
}
ConfigFile config = new ConfigFile(path);
Log.i(TAG, "Config readback from: " + path);
@ -164,15 +156,7 @@ public final class UserPreferences
public static void updateConfigFile(Context ctx)
{
String path = getDefaultConfigPath(ctx);
ConfigFile config;
try
{
config = new ConfigFile(new File(path));
}
catch (IOException e)
{
config = new ConfigFile();
}
ConfigFile config = new ConfigFile(path);
Log.i(TAG, "Writing config to: " + path);
@ -315,7 +299,7 @@ public final class UserPreferences
try
{
config.write(new File(path));
config.write(path);
}
catch (IOException e)
{