-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <!-- The buffer use to read files (in bytes). Default: 0.5 Mb -->
- <integer name="read_buffer_size">512</integer>
-
- <!-- The maximum file size that the app is able to read. Beyond this size the user will get a
- warning message (in bytes). Default: 5 Mb
- -->
- <integer name="max_read_file_size">5120</integer>
+ <!-- The size of the buffers use by the console (in bytes). Default: 0.5 Mb -->
+ <integer name="buffer_size">512</integer>
</resources>
\ No newline at end of file
<!-- Loading waiting message -->
<string name="loading_message">Loading…</string>
<!-- Computing message -->
- <string name="computing_message">Computing… <xliff:g id="data">%1$s</xliff:g></string>
+ <string name="computing_message"><xliff:g id="data">%1$s</xliff:g></string>
<!-- Computing new line message -->
- <string name="computing_message_ln">Computing…\n<xliff:g id="data">%1$s</xliff:g></string>
+ <string name="computing_message_ln"><xliff:g id="data">%1$s</xliff:g></string>
<!-- Cancelled message -->
<string name="cancelled_message">Cancelled.</string>
<!-- Error message -->
<string name="search_terms"><b>Terms:</b> <xliff:g id="terms">%1$s</xliff:g></string>
<!-- Some terms of the search are too small. The operation can be very costly -->
<string name="search_few_characters_msg">Some of terms of the search has few characters. In
- this situation, the operation can be very costly in time and system resources.\n\n
+ this situation, the operation that can be very costly in time and system resources.\n\n
Do you want to cancel the search?</string>
<!-- Searching dialog title -->
<string name="pref_compute_folder_statistics">Compute folder statistics</string>
<!-- Preferences * General * Compute folder statistics summary on -->
<string name="pref_compute_folder_statistics_on">Compute folder statistics in folder
- properties dialog.</string>
+ properties dialog.\n\nWarning! Compute folder statistics is a operation very costly in time
+ and system resources.</string>
<!-- Preferences * General * Compute folder statistics summary off -->
- <string name="pref_compute_folder_statistics_off">Not compute folder statistics in folder
+ <string name="pref_compute_folder_statistics_off">No compute folder statistics in folder
properties dialog.</string>
<!-- Preferences * General * Advanced settings category -->
<string name="pref_general_advanced_settings_category">Advanced settings</string>
with the expected command for retrieve the exit code of the executed command
-->
<CommandList xmlns:androcommandId="http://schemas.android.com/apk/res/android">
+ <!-- Start code (append to commands; for retrieve the exit code) -->
+ <startcode commandId="startcode" commandPath="/system/xbin/echo %1$s0%2$s ; " />
<!-- Exit code (append to commands; for retrieve the exit code) -->
<exitcode commandId="exitcode" commandPath=" ; /system/xbin/echo %1$s$?%2$s" />
try {
sBackgroundConsole =
new ConsoleHolder(
- ConsoleBuilder.createNonPrivilegedConsole(FileHelper.ROOT_DIRECTORY));
+ ConsoleBuilder.createNonPrivilegedConsole(
+ getApplicationContext(), FileHelper.ROOT_DIRECTORY));
} catch (Exception e) {
Log.e(TAG,
"Background console creation failed. " + //$NON-NLS-1$
// Command list XML tags
private static final String TAG_COMMAND_LIST = "CommandList"; //$NON-NLS-1$
private static final String TAG_COMMAND = "command"; //$NON-NLS-1$
+ private static final String TAG_STARTCODE = "startcode"; //$NON-NLS-1$
private static final String TAG_EXITCODE = "exitcode"; //$NON-NLS-1$
private final String mId;
private String mArgs; // The real arguments
private final Object[] mCmdArgs; //This arguments to be formatted
+ private static String sStartCodeCmd;
private static String sExitCodeCmd;
/**
* @throws InvalidCommandDefinitionException If the command is not present or has an
* invalid definition
*/
+ public static synchronized String getStartCodeCommandInfo(
+ Resources resources) throws InvalidCommandDefinitionException {
+ //Singleton
+ if (sStartCodeCmd != null) {
+ return new String(sStartCodeCmd);
+ }
+
+ //Read the command list xml file
+ XmlResourceParser parser = resources.getXml(R.xml.command_list);
+
+ try {
+ //Find the root element
+ XmlUtils.beginDocument(parser, TAG_COMMAND_LIST);
+ while (true) {
+ XmlUtils.nextElement(parser);
+ String element = parser.getName();
+ if (element == null) {
+ break;
+ }
+
+ if (TAG_STARTCODE.equals(element)) {
+ CharSequence path = parser.getAttributeValue(R.styleable.Command_commandPath);
+ if (path == null) {
+ throw new InvalidCommandDefinitionException(
+ TAG_STARTCODE + ": path is null"); //$NON-NLS-1$
+ }
+
+ //Save paths
+ sStartCodeCmd = path.toString();
+ return new String(sStartCodeCmd);
+ }
+ }
+ } catch (XmlPullParserException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ parser.close();
+ }
+
+ //Command not found
+ throw new InvalidCommandDefinitionException(TAG_STARTCODE);
+ }
+
+ /**
+ * Method that returns the exit code command info.
+ *
+ * @param resources The application resource manager
+ * @return String The exit code command info
+ * @throws InvalidCommandDefinitionException If the command is not present or has an
+ * invalid definition
+ */
public static synchronized String getExitCodeCommandInfo(
Resources resources) throws InvalidCommandDefinitionException {
//Singleton
//Retrieve parent directory information
if (mode.compareTo(LIST_MODE.DIRECTORY) == 0) {
//Resolve the parent directory
- this.mParentDir =
- CommandHelper.getAbsolutePath(
- ExplorerApplication.getInstance().getApplicationContext(), src, console);
+ if (src.compareTo(FileHelper.ROOT_DIRECTORY) == 0) {
+ this.mParentDir = null;
+ } else {
+ this.mParentDir =
+ CommandHelper.getAbsolutePath(
+ ExplorerApplication.
+ getInstance().getApplicationContext(), src, console);
+ }
} else {
//Get the absolute path
try {
- // From console
+ this.mParentDir = new File(src).getCanonicalFile().getParent();
+
+ } catch (Exception e) {
+ // Try to resolve from a console
String abspath =
CommandHelper.getAbsolutePath(
- ExplorerApplication.getInstance().getApplicationContext(), src, console);
+ ExplorerApplication.getInstance().
+ getApplicationContext(), src, console);
//Resolve the parent directory
this.mParentDir =
CommandHelper.getParentDir(
ExplorerApplication.getInstance().getApplicationContext(),
abspath, console);
-
- } catch (Exception e) {
- // From Java
- this.mParentDir = new File(src).getCanonicalFile().getParent();
- if (this.mParentDir == null) {
- throw new CommandNotFoundException(ID_LS_INFO, e);
- }
}
}
}
}
//Now if not is the root directory
- if (this.mParentDir.compareTo(FileHelper.ROOT_DIRECTORY) != 0 &&
+ if (this.mParentDir != null &&
+ this.mParentDir.compareTo(FileHelper.ROOT_DIRECTORY) != 0 &&
this.mMode.compareTo(LIST_MODE.DIRECTORY) == 0) {
this.mFiles.add(0, new ParentDirectory(new File(this.mParentDir).getParent()));
}
ConsoleHolder holder = null;
try {
//Create the console, destroy the current console, and marks as current
- holder = new ConsoleHolder(createNonPrivilegedConsole(FileHelper.ROOT_DIRECTORY));
+ holder = new ConsoleHolder(
+ createNonPrivilegedConsole(context, FileHelper.ROOT_DIRECTORY));
destroyConsole();
sHolder = holder;
return true;
? new ConsoleHolder(
createPrivilegedConsole(context, FileHelper.ROOT_DIRECTORY))
: new ConsoleHolder(
- createNonPrivilegedConsole(FileHelper.ROOT_DIRECTORY));
+ createNonPrivilegedConsole(context, FileHelper.ROOT_DIRECTORY));
}
return sHolder.getConsole();
}
/**
* Method that creates a new non privileged console.
*
+ * @param context The current context
* @param initialDirectory The initial directory of the console
* @return Console The non privileged console
* @throws FileNotFoundException If the initial directory not exists
* @throws ConsoleAllocException If the console can't be allocated
* @see NonPriviledgeConsole
*/
- public static Console createNonPrivilegedConsole(String initialDirectory)
+ public static Console createNonPrivilegedConsole(Context context, String initialDirectory)
throws FileNotFoundException, IOException,
InvalidCommandDefinitionException, ConsoleAllocException {
NonPriviledgeConsole console = new NonPriviledgeConsole(initialDirectory);
+ console.setBufferSize(context.getResources().getInteger(R.integer.buffer_size));
console.alloc();
return console;
}
ConsoleAllocException, InsufficientPermissionsException {
try {
PrivilegedConsole console = new PrivilegedConsole(initialDirectory);
+ console.setBufferSize(context.getResources().getInteger(R.integer.buffer_size));
console.alloc();
if (console.getIdentity().getUser().getId() != ROOT_UID) {
//The console is not a privileged console
private static final long DEFAULT_TIMEOUT = 20000L;
+ private static final int DEFAULT_BUFFER = 512;
+
//Shell References
private final Shell mShell;
private final String mInitialDirectory;
private Process mProc = null;
private Program mActiveCommand = null;
private boolean mCancelled;
+ private boolean mStarted;
//Buffers
private InputStream mIn = null;
private StringBuffer mSbErr = null;
private final SecureRandom mRandom;
- private String mControlPattern;
+ private String mStartControlPattern;
+ private String mEndControlPattern;
+
+ private int mBufferSize;
private final ShellExecutableFactory mExecutableFactory;
this.mShell = shell;
this.mExecutableFactory = new ShellExecutableFactory(this);
+ this.mBufferSize = DEFAULT_BUFFER;
+
//Resolve and checks the initial directory
File f = new File(initialDirectory);
while (FileHelper.isSymlink(f)) {
return this.mIdentity;
}
+
+
+ /**
+ * Method that returns the buffer size
+ *
+ * @return int The buffer size
+ */
+ public int getBufferSize() {
+ return this.mBufferSize;
+ }
+
+ /**
+ * Method that sets the buffer size
+ *
+ * @param bufferSize the The buffer size
+ */
+ public void setBufferSize(int bufferSize) {
+ this.mBufferSize = bufferSize;
+ }
+
/**
* {@inheritDoc}
*/
this.mActiveCommand = program;
//Reset the buffers
+ this.mStarted = false;
+ this.mCancelled = false;
this.mSbIn = new StringBuffer();
this.mSbErr = new StringBuffer();
- //Random exit identifiers
- String exitId1 =
+ //Random start/end identifiers
+ String startId1 =
+ String.format("/#%d#/", Long.valueOf(this.mRandom.nextLong())); //$NON-NLS-1$
+ String startId2 =
String.format("/#%d#/", Long.valueOf(this.mRandom.nextLong())); //$NON-NLS-1$
- String exitId2 =
+ String endId1 =
+ String.format("/#%d#/", Long.valueOf(this.mRandom.nextLong())); //$NON-NLS-1$
+ String endId2 =
String.format("/#%d#/", Long.valueOf(this.mRandom.nextLong())); //$NON-NLS-1$
//Create command string
//This control code is unique in every invocation and is secure random
//generated (control code 1 + exit code + control code 2)
try {
- this.mControlPattern = exitId1 + "\\d{1,3}" + exitId2; //$NON-NLS-1$
- String exitCmd =
+ this.mStartControlPattern = startId1 + "\\d{1,3}" + startId2 + "\\n"; //$NON-NLS-1$ //$NON-NLS-2$
+ this.mEndControlPattern = endId1 + "\\d{1,3}" + endId2; //$NON-NLS-1$
+ String startCmd =
+ Command.getStartCodeCommandInfo(
+ ExplorerApplication.getInstance().getResources());
+ startCmd = String.format(
+ startCmd, "'" + startId1 +//$NON-NLS-1$
+ "'", "'" + startId2 + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ String endCmd =
Command.getExitCodeCommandInfo(
ExplorerApplication.getInstance().getResources());
- exitCmd = String.format(
- exitCmd, "'" + exitId1 + "'", "'" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- + exitId2 + "'"); //$NON-NLS-1$
+ endCmd = String.format(
+ endCmd, "'" + endId1 + //$NON-NLS-1$
+ "'", "'" + endId2 + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
StringBuilder sb = new StringBuilder()
+ .append(startCmd)
+ .append(" ") //$NON-NLS-1$
.append(cmd)
.append(" ") //$NON-NLS-1$
.append(args)
.append(" ") //$NON-NLS-1$
- .append(exitCmd)
+ .append(endCmd)
.append(FileHelper.NEWLINE);
this.mOut.write(sb.toString().getBytes());
} catch (InvalidCommandDefinitionException icdEx) {
public void run() {
int read = 0;
try {
- //Notify the ending
+ //Notify the start
if (ShellConsole.this.mActiveCommand != null
&& ShellConsole.this.mActiveCommand instanceof AsyncResultProgram) {
AsyncResultProgram program =
break;
}
StringBuffer sb = new StringBuffer();
- ShellConsole.this.mSbIn.append((char)r);
- sb.append((char)r);
-
- //Notify asynchronous partial data
- if (ShellConsole.this.mActiveCommand != null
- && ShellConsole.this.mActiveCommand instanceof AsyncResultProgram) {
- AsyncResultProgram program =
- ((AsyncResultProgram)ShellConsole.this.mActiveCommand);
- program.parsePartialResult(new String(new char[]{(char)r}));
+ if (!ShellConsole.this.mCancelled) {
+ ShellConsole.this.mSbIn.append((char)r);
+ if (!ShellConsole.this.mStarted) {
+ ShellConsole.this.mStarted =
+ isCommandStarted(ShellConsole.this.mSbIn);
+ sb.append(ShellConsole.this.mSbIn.toString());
+ } else {
+ sb.append((char)r);
+ }
+
+ //Notify asynchronous partial data
+ if (ShellConsole.this.mStarted &&
+ ShellConsole.this.mActiveCommand != null &&
+ ShellConsole.this.mActiveCommand instanceof AsyncResultProgram) {
+ AsyncResultProgram program =
+ ((AsyncResultProgram)ShellConsole.this.mActiveCommand);
+ program.parsePartialResult(new String(new char[]{(char)r}));
+ }
}
//Has more data? Read with available as more as exists
int count = 0;
while (in.available() > 0 && count < 10) {
count++;
- byte[] data = new byte[in.available()];
+ int available = Math.min(in.available(),
+ ShellConsole.this.mBufferSize);
+ byte[] data = new byte[available];
read = in.read(data);
+
+ // Exit if active command is canceled
+ if (ShellConsole.this.mCancelled) continue;
+
final String s = new String(data, 0, read);
ShellConsole.this.mSbIn.append(s);
- sb.append(s);
+ if (!ShellConsole.this.mStarted) {
+ ShellConsole.this.mStarted =
+ isCommandStarted(ShellConsole.this.mSbIn);
+ sb.append(ShellConsole.this.mSbIn.toString());
+ } else {
+ sb.append(s);
+ }
//Notify asynchronous partial data
- if (ShellConsole.this.mActiveCommand != null
- && ShellConsole.this.mActiveCommand instanceof AsyncResultProgram) {
+ if (ShellConsole.this.mActiveCommand != null &&
+ ShellConsole.this.mActiveCommand instanceof AsyncResultProgram) {
AsyncResultProgram program =
((AsyncResultProgram)ShellConsole.this.mActiveCommand);
program.parsePartialResult(s);
}
-
//Wait for buffer to be filled
try {
Thread.sleep(50L);
}
//Check if the command has finished
- if (isCommandFinish(sb)) {
+ if (isCommandFinished(ShellConsole.this.mSbIn)) {
//Notify the end
notifyProcessFinished();
}
}
- //Audit
- if (isTrace()) {
+ //Audit (if not canceled)
+ if (!ShellConsole.this.mCancelled && isTrace()) {
Log.v(TAG,
String.format("stdin: %s", sb.toString())); //$NON-NLS-1$
}
//Asynchronous programs can cause a lot of output, control buffers
//for a low memory footprint
- if (ShellConsole.this.mActiveCommand != null
- && ShellConsole.this.mActiveCommand instanceof AsyncResultProgram) {
+ if (ShellConsole.this.mActiveCommand != null &&
+ ShellConsole.this.mActiveCommand instanceof AsyncResultProgram) {
trimBuffer(ShellConsole.this.mSbIn);
trimBuffer(ShellConsole.this.mSbErr);
}
break;
}
StringBuffer sb = new StringBuffer();
- ShellConsole.this.mSbErr.append((char)r);
- sb.append((char)r);
+ if (!ShellConsole.this.mCancelled) {
+ ShellConsole.this.mSbErr.append((char)r);
+ sb.append((char)r);
+ }
//Has more data? Read with available as more as exists
//or maximum loop count is rebased
int count = 0;
while (err.available() > 0 && count < 10) {
count++;
- byte[] data = new byte[err.available()];
+ int available = Math.min(err.available(),
+ ShellConsole.this.mBufferSize);
+ byte[] data = new byte[available];
read = err.read(data);
+
+ // Exit if active command is canceled
+ if (ShellConsole.this.mCancelled) continue;
+
String s = new String(data, 0, read);
ShellConsole.this.mSbErr.append(s);
sb.append(s);
}
}
- //Audit
- if (isTrace()) {
+ //Audit (if not canceled)
+ if (!ShellConsole.this.mCancelled && isTrace()) {
Log.v(TAG,
String.format("stderr: %s", sb.toString())); //$NON-NLS-1$
}
//Asynchronous programs can cause a lot of output, control buffers
//for a low memory footprint
- if (ShellConsole.this.mActiveCommand != null
- && ShellConsole.this.mActiveCommand instanceof AsyncResultProgram) {
+ if (ShellConsole.this.mActiveCommand != null &&
+ ShellConsole.this.mActiveCommand instanceof AsyncResultProgram) {
trimBuffer(ShellConsole.this.mSbIn);
trimBuffer(ShellConsole.this.mSbErr);
}
}
/**
+ * Method that returns if the command has started by checking the
+ * standard input buffer. This method also removes the control start command
+ * from the buffer, if it's present, leaving in the buffer the new data bytes.
+ *
+ * @param stdin The standard in buffer
+ * @return boolean If the command has started
+ */
+ private boolean isCommandStarted(StringBuffer stdin) {
+ Pattern pattern = Pattern.compile(this.mStartControlPattern);
+ Matcher matcher = pattern.matcher(stdin.toString());
+ if (matcher.find()) {
+ stdin.replace(0, matcher.end(), ""); //$NON-NLS-1$
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Method that returns if the command has finished by checking the
* standard input buffer.
*
* @param stdin The standard in buffer
* @return boolean If the command has finished
*/
- private boolean isCommandFinish(StringBuffer stdin) {
- Pattern pattern = Pattern.compile(this.mControlPattern);
+ private boolean isCommandFinished(StringBuffer stdin) {
+ Pattern pattern = Pattern.compile(this.mEndControlPattern);
Matcher matcher = pattern.matcher(stdin.toString());
return matcher.find();
}
* @return int The exit code of the last executed command
*/
private int getExitCode(StringBuffer stdin) {
+ // If process was canceled, don't expect a exit code.
+ // Returns always 143 code
+ if (this.mCancelled) {
+ return 143;
+ }
+
+ // Parse the stdin seeking exit code pattern
String txt = stdin.toString();
- Pattern pattern = Pattern.compile(this.mControlPattern);
+ Pattern pattern = Pattern.compile(this.mEndControlPattern);
Matcher matcher = pattern.matcher(txt);
if (matcher.find()) {
this.mSbIn = new StringBuffer(txt.substring(0, matcher.start()));
exitTxt.indexOf("#/") + 2, //$NON-NLS-1$
exitTxt.indexOf("/#", 2))); //$NON-NLS-1$
}
- if (this.mCancelled) {
- return 143;
- }
return 255;
}
/**NON BLOCK**/
}
this.mCancelled = true;
+ notifyProcessFinished();
this.mSync.notify();
return this.mCancelled;
}
"%s%s/%s", //$NON-NLS-1$
"content://", //$NON-NLS-1$
BookmarksContentProvider.AUTHORITY,
- "/bookmarks") //$NON-NLS-1$
- );
+ "/bookmarks")); //$NON-NLS-1$
/**
* The directory of the bookmark
*/
public String getFullPath() {
if (FileHelper.isRootDirectory(this)) {
+ return FileHelper.ROOT_DIRECTORY;
+ } else if (FileHelper.isParentRootDirectory(this)) {
+ if (this.mParent == null) {
+ return FileHelper.ROOT_DIRECTORY + this.mName;
+ }
return this.mParent + this.mName;
}
return this.mParent + File.separator + this.mName;
* @hide
*/
SETTINGS_COMPUTE_FOLDER_STATISTICS(
- "cm_explorer_compute_folder_statistics", Boolean.TRUE), //$NON-NLS-1$
+ "cm_explorer_compute_folder_statistics", Boolean.FALSE), //$NON-NLS-1$
/**
* When to use case sensitive comparison in sorting of files
* @hide
final Resources res = this.mContext.getResources();
if (cancelled) {
- FsoPropertiesDialog.this.mTvSize.setText(R.string.cancelled_message);
- FsoPropertiesDialog.this.mTvContains.setText(R.string.cancelled_message);
+ try {
+ FsoPropertiesDialog.this.mTvSize.setText(R.string.cancelled_message);
+ FsoPropertiesDialog.this.mTvContains.setText(R.string.cancelled_message);
+ } catch (Throwable e) {/**NON BLOCK**/}
// End of drawing
this.mDrawingFolderUsage = false;
* @return boolean if the file system object if the root directory
*/
public static boolean isRootDirectory(FileSystemObject fso) {
+ if (fso.getName() == null) return true;
+ return fso.getName().compareTo(FileHelper.ROOT_DIRECTORY) == 0;
+ }
+
+ /**
+ * Method that returns if the parent file system object if the root directory.
+ *
+ * @param fso The parent file system object to check
+ * @return boolean if the parent file system object if the root directory
+ */
+ public static boolean isParentRootDirectory(FileSystemObject fso) {
+ if (fso.getParent() == null) return true;
return fso.getParent().compareTo(FileHelper.ROOT_DIRECTORY) == 0;
}
// Can ensure this?)
//<name>:
// - if object is a symlink, the value must be "link name -> real name"
+ // - If the name is void, then assume that it is the root directory (/)
//
public static FileSystemObject toFileSystemObject(
final String parent, final String src, final boolean quick) throws ParseException {
//6.- Extract object name
String szName = szEndLine;
+ if (szName.trim().length() == 0) {
+ // Assume that the object name is the root folder
+ szName = FileHelper.ROOT_DIRECTORY;
+ }
String szLink = null;
if (type == Symlink.UNIX_ID) {
//"link name -> real name"
if (isRootConsoleNeeded()) {
this.mConsole = ConsoleBuilder.createPrivilegedConsole(getContext(), INITIAL_DIR);
} else {
- this.mConsole = ConsoleBuilder.createNonPrivilegedConsole(INITIAL_DIR);
+ this.mConsole = ConsoleBuilder.createNonPrivilegedConsole(getContext(), INITIAL_DIR);
}
super.setUp();
* Method that performs a test over creating a non privileged console.
*
* @throws Exception If test failed
- * @{link {@link ConsoleBuilder#createNonPrivilegedConsole(String)}
+ * @{link {@link ConsoleBuilder#createNonPrivilegedConsole(android.content.Context, String)}
*/
- @SuppressWarnings("static-method")
public void testCreateNonPrivilegedConsole() throws Exception {
- Console console = ConsoleBuilder.createNonPrivilegedConsole(PATH);
+ Console console = ConsoleBuilder.createNonPrivilegedConsole(getContext(), PATH);
try {
assertNotNull("console==null", console); //$NON-NLS-1$
} finally {