OSDN Git Service

CMFM: CYAN-285 - Add ability to calculate file checksums
authorJorge Ruesga <jorge@ruesga.com>
Sat, 16 Mar 2013 17:14:21 +0000 (18:14 +0100)
committerJorge Ruesga <jorge@ruesga.com>
Sat, 16 Mar 2013 17:20:12 +0000 (18:20 +0100)
Added support for compute MD5 and SHA1 file checksums

Patchset 2: Remove trailing whitespaces

Change-Id: I46cbd0d451eea76e259bdddc485774bbfd34cdc0
JIRA: https://jira.cyanogenmod.org/browse/CYAN-285
Bugfix: CYAN-285
Signed-off-by: Jorge Ruesga <jorge@ruesga.com>
25 files changed:
res/drawable-hdpi/ic_holo_light_copy.png [new file with mode: 0644]
res/drawable-mdpi/ic_holo_light_copy.png [new file with mode: 0644]
res/drawable-xhdpi/ic_holo_light_copy.png [new file with mode: 0644]
res/layout/compute_checksum_dialog.xml [new file with mode: 0644]
res/menu/actions.xml
res/values/overlay.xml
res/values/strings.xml
res/values/theme.xml
res/xml/command_list.xml
src/com/cyanogenmod/filemanager/commands/ChecksumExecutable.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/commands/ExecutableCreator.java
src/com/cyanogenmod/filemanager/commands/java/ChecksumCommand.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/commands/java/JavaExecutableCreator.java
src/com/cyanogenmod/filemanager/commands/shell/ChecksumCommand.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/commands/shell/ShellExecutableCreator.java
src/com/cyanogenmod/filemanager/ui/dialogs/ActionsDialog.java
src/com/cyanogenmod/filemanager/ui/dialogs/ComputeChecksumDialog.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/ui/dialogs/ExecutionDialog.java
src/com/cyanogenmod/filemanager/ui/policy/InfoActionPolicy.java
src/com/cyanogenmod/filemanager/util/CommandHelper.java
tests/src/com/cyanogenmod/filemanager/commands/shell/ChecksumCommandTest.java [new file with mode: 0644]
themes/res/drawable-hdpi/ic_holo_dark_copy.png [new file with mode: 0644]
themes/res/drawable-mdpi/ic_holo_dark_copy.png [new file with mode: 0644]
themes/res/drawable-xhdpi/ic_holo_dark_copy.png [new file with mode: 0644]
themes/res/values/dark_theme.xml

diff --git a/res/drawable-hdpi/ic_holo_light_copy.png b/res/drawable-hdpi/ic_holo_light_copy.png
new file mode 100644 (file)
index 0000000..623b715
Binary files /dev/null and b/res/drawable-hdpi/ic_holo_light_copy.png differ
diff --git a/res/drawable-mdpi/ic_holo_light_copy.png b/res/drawable-mdpi/ic_holo_light_copy.png
new file mode 100644 (file)
index 0000000..efb2445
Binary files /dev/null and b/res/drawable-mdpi/ic_holo_light_copy.png differ
diff --git a/res/drawable-xhdpi/ic_holo_light_copy.png b/res/drawable-xhdpi/ic_holo_light_copy.png
new file mode 100644 (file)
index 0000000..00bff33
Binary files /dev/null and b/res/drawable-xhdpi/ic_holo_light_copy.png differ
diff --git a/res/layout/compute_checksum_dialog.xml b/res/layout/compute_checksum_dialog.xml
new file mode 100644 (file)
index 0000000..c737b07
--- /dev/null
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The CyanogenMod Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+  android:layout_width="match_parent"
+  android:layout_height="match_parent"
+  android:layout_margin="@dimen/extra_large_margin"
+  android:orientation="vertical">
+
+  <TextView
+    android:id="@+id/checksum_filename_label"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_marginLeft="@dimen/extra_large_margin"
+    android:layout_marginRight="@dimen/extra_large_margin"
+    android:layout_marginTop="@dimen/extra_large_margin"
+    android:gravity="left|center_vertical"
+    android:text="@string/compute_checksum_filename_label"
+    android:textAppearance="@style/primary_text_appearance" />
+
+  <TextView
+    android:id="@+id/checksum_filename"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_marginLeft="@dimen/extra_large_margin"
+    android:layout_marginRight="@dimen/extra_large_margin"
+    android:gravity="left|center_vertical"
+    android:textAppearance="@style/secondary_text_appearance" />
+
+  <TextView
+    android:id="@+id/checksum_md5_label"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_marginLeft="@dimen/extra_large_margin"
+    android:layout_marginRight="@dimen/extra_large_margin"
+    android:layout_marginTop="@dimen/extra_large_margin"
+    android:gravity="left|center_vertical"
+    android:text="@string/compute_checksum_md5_label"
+    android:textAppearance="@style/primary_text_appearance" />
+
+  <RelativeLayout
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginLeft="@dimen/extra_large_margin"
+    android:layout_marginRight="@dimen/extra_large_margin">
+
+    <com.cyanogenmod.filemanager.ui.widgets.ButtonItem
+      android:id="@+id/bt_md5_clipboard"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_alignParentTop="true"
+      android:layout_alignParentRight="true"
+      android:contentDescription="@string/copy_text_cd" />
+
+    <EditText
+      android:id="@+id/checksum_md5"
+      android:layout_width="match_parent"
+      android:layout_height="64dp"
+      android:singleLine="false"
+      android:gravity="top|left"
+      android:cursorVisible="true"
+      android:background="@color/console_bg"
+      android:imeOptions="actionNone|flagNoFullscreen"
+      android:inputType="textMultiLine|textImeMultiLine"
+      android:focusable="false"
+      android:layout_toLeftOf="@id/bt_md5_clipboard"
+      android:layout_alignTop="@id/bt_md5_clipboard"
+      android:layout_alignBottom="@id/bt_md5_clipboard"
+      android:layout_marginRight="@dimen/default_margin"
+      android:text="@string/compute_checksum_computing_checksum_msg"
+      android:textAppearance="@style/console_text_appearance" />
+  </RelativeLayout>
+
+  <TextView
+    android:id="@+id/checksum_sha1_label"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_marginLeft="@dimen/extra_large_margin"
+    android:layout_marginRight="@dimen/extra_large_margin"
+    android:layout_marginTop="@dimen/extra_large_margin"
+    android:gravity="left|center_vertical"
+    android:text="@string/compute_checksum_sha1_label"
+    android:textAppearance="@style/primary_text_appearance" />
+
+  <RelativeLayout
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginLeft="@dimen/extra_large_margin"
+    android:layout_marginRight="@dimen/extra_large_margin"
+    android:layout_marginBottom="@dimen/extra_large_margin">
+
+    <com.cyanogenmod.filemanager.ui.widgets.ButtonItem
+      android:id="@+id/bt_sha1_clipboard"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_alignParentTop="true"
+      android:layout_alignParentRight="true"
+      android:contentDescription="@string/copy_text_cd" />
+
+    <EditText
+      android:id="@+id/checksum_sha1"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:singleLine="false"
+      android:gravity="top|left"
+      android:cursorVisible="true"
+      android:background="@color/console_bg"
+      android:imeOptions="actionNone|flagNoFullscreen"
+      android:inputType="textMultiLine|textImeMultiLine"
+      android:focusable="false"
+      android:layout_toLeftOf="@id/bt_sha1_clipboard"
+      android:layout_alignTop="@id/bt_sha1_clipboard"
+      android:layout_alignBottom="@id/bt_sha1_clipboard"
+      android:layout_marginRight="@dimen/default_margin"
+      android:text="@string/compute_checksum_computing_checksum_msg"
+      android:textAppearance="@style/console_text_appearance" />
+  </RelativeLayout>
+
+</LinearLayout>
index 4498286..305c6bb 100644 (file)
       android:showAsAction="ifRoom"
       android:title="@string/actions_menu_add_shortcut"/>
     <item
+      android:id="@+id/mnu_actions_compute_checksum"
+      android:showAsAction="ifRoom"
+      android:title="@string/actions_menu_compute_checksum"/>
+    <item
       android:id="@+id/mnu_actions_open_parent_folder"
       android:showAsAction="ifRoom"
       android:title="@string/actions_menu_open_parent_folder"/>
index 63ce69f..dcea6e6 100644 (file)
@@ -68,7 +68,9 @@
     /system/xbin/unlzma,
     /system/xbin/unxz,
     /system/xbin/unzip,
-    /system/xbin/xargs
+    /system/xbin/xargs,
+    /system/xbin/md5sum,
+    /system/xbin/sha1sum
   </string>
 
   <!-- The mounts file -->
index 9a15cfc..c95af3d 100644 (file)
   <string name="cancelled_message">Cancelled.</string>
   <!-- Error message -->
   <string name="error_message">Error.</string>
+  <!-- Copy text content description -->
+  <string name="copy_text_cd">Tap to copy text to clipboard</string>
+  <!-- Copy text content message -->
+  <string name="copy_text_msg">Text copied to clipboard</string>
 
   <!-- Warning dialog title -->
   <string name="warning_title">Warning</string>
   <string name="actions_menu_add_shortcut">Add shortcut</string>
   <!-- Actions Dialog * Menu * Open parent folder -->
   <string name="actions_menu_open_parent_folder">Open parent</string>
+  <!-- Actions Dialog * Menu * Compute checksum -->
+  <string name="actions_menu_compute_checksum">Compute checksum</string>
 
   <!-- Actions * Ask user prior to do an undone operation. Dialog message -->
   <string name="actions_ask_undone_operation_msg">
   <string name="execution_console_script_execution_time_text">
     <xliff:g id="seconds">%1$s</xliff:g> sec.</string>
 
+  <!-- Compute checksum * Title -->
+  <string name="compute_checksum_title">Compute checksum</string>
+  <!-- Compute checksum * The file name label -->
+  <string name="compute_checksum_filename_label">File:</string>
+  <!-- Compute checksum * The MD5 label -->
+  <string name="compute_checksum_md5_label" translatable="false">MD5:</string>
+  <!-- Compute checksum * The SHA1 label -->
+  <string name="compute_checksum_sha1_label" translatable="false">SHA-1:</string>
+  <!-- Compute checksum * The computing checksum message-->
+  <string name="compute_checksum_computing_checksum_msg">Computing checksum\u2026</string>
+
   <!-- Mime/Types * Folder -->
   <string name="mime_folder">Folder</string>
   <!-- Mime/Types * Symlink -->
index ae16b76..629b234 100644 (file)
   <drawable name="ic_usb_drawable">@drawable/ic_holo_light_usb</drawable>
   <drawable name="ic_user_defined_bookmark_drawable">@drawable/ic_holo_light_user_defined_bookmark</drawable>
   <drawable name="ic_history_search_drawable">@drawable/ic_holo_light_history_search</drawable>
+  <drawable name="ic_copy_drawable">@drawable/ic_holo_light_copy</drawable>
 
   <!-- Disk usage graph -->
   <color name="disk_usage_total_color">@color/disk_usage_total</color>
index 3138195..1d61a67 100644 (file)
@@ -80,6 +80,7 @@
   <!-- Misc -->
   <command commandId="dirname" commandPath="/system/xbin/dirname" commandArgs="%1$s" />
   <command commandId="echo" commandPath="/system/xbin/echo" commandArgs="%1$s" />
+  <command commandId="checksum" commandPath="/system/xbin/md5sum" commandArgs="%1$s &amp;&amp; /system/xbin/sha1sum %1$s" />
 
   <!-- Process control and info -->
   <command commandId="pid_shell" commandPath="/system/xbin/echo" commandArgs="$$" />
diff --git a/src/com/cyanogenmod/filemanager/commands/ChecksumExecutable.java b/src/com/cyanogenmod/filemanager/commands/ChecksumExecutable.java
new file mode 100644 (file)
index 0000000..43d518d
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2012 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.cyanogenmod.filemanager.commands;
+
+/**
+ * An interface that represents an executable for calculate checksum of file system objects.
+ */
+public interface ChecksumExecutable extends AsyncResultExecutable {
+
+    /**
+     * Checksum enumerations
+     */
+    public enum CHECKSUMS {
+        /**
+         * MD5 digest algorithm
+         */
+        MD5,
+        /**
+         * SHA-1 digest algorithm
+         */
+        SHA1
+    }
+
+    /**
+     * Method that returns the calculated MD5 [0] and SHA-1 [1] digests
+     *
+     * @return String[] The calculated MD5 [0] and SHA-1 [1] digests
+     */
+    String[] getResult();
+
+    /**
+     * Method that returns a calculated digest checksum
+     *
+     * @param checksum The checksum to return
+     * @return String The calculated digest to return
+     */
+    String getChecksum(CHECKSUMS checksum);
+}
index ea7b958..f5c2f16 100644 (file)
@@ -518,4 +518,19 @@ public interface ExecutableCreator {
             throws CommandNotFoundException,
             NoSuchFileOrDirectory, InsufficientPermissionsException;
 
+    /**
+     * Method that creates an executable for calculate checksums of file system objects.
+     *
+     * @param src The compressed file
+     * @param asyncResultListener The listener where to return partial results
+     * @return ChecksumExecutable A {@link ChecksumExecutable} executable implementation reference
+     * @throws CommandNotFoundException If the executable can't be created
+     * @throws NoSuchFileOrDirectory If the file or directory was not found
+     * @throws InsufficientPermissionsException If an operation requires elevated permissions
+     */
+    ChecksumExecutable createChecksumExecutable(
+            String src, AsyncResultListener asyncResultListener)
+            throws CommandNotFoundException,
+            NoSuchFileOrDirectory, InsufficientPermissionsException;
+
 }
diff --git a/src/com/cyanogenmod/filemanager/commands/java/ChecksumCommand.java b/src/com/cyanogenmod/filemanager/commands/java/ChecksumCommand.java
new file mode 100644 (file)
index 0000000..d18c557
--- /dev/null
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2012 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.cyanogenmod.filemanager.commands.java;
+
+import android.util.Log;
+
+import com.android.internal.util.HexDump;
+import com.cyanogenmod.filemanager.commands.AsyncResultListener;
+import com.cyanogenmod.filemanager.commands.ChecksumExecutable;
+import com.cyanogenmod.filemanager.console.ExecutionException;
+import com.cyanogenmod.filemanager.console.InsufficientPermissionsException;
+import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.security.MessageDigest;
+
+/**
+ * A class for calculate MD5 and SHA-1 checksums of a file system object.<br />
+ * <br />
+ * Partial results are returned in order (MD5 -> SHA1)
+ */
+public class ChecksumCommand extends Program implements ChecksumExecutable {
+
+    private static final String TAG = "ChecksumCommand"; //$NON-NLS-1$
+
+    private final File mSrc;
+    private final String[] mChecksums;
+    private final AsyncResultListener mAsyncResultListener;
+
+    private boolean mCancelled;
+    private final Object mSync = new Object();
+
+    /**
+     * Constructor of <code>ChecksumCommand</code>.
+     *
+     * @param src The source file
+     * @param asyncResultListener The partial result listener
+     */
+    public ChecksumCommand(
+            String src, AsyncResultListener asyncResultListener) {
+        super();
+        this.mAsyncResultListener = asyncResultListener;
+        this.mChecksums = new String[]{null, null};
+        this.mSrc = new File(src);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isAsynchronous() {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void execute() throws InsufficientPermissionsException,
+        NoSuchFileOrDirectory, ExecutionException {
+
+        if (isTrace()) {
+            Log.v(TAG,
+                    String.format("Calculating checksums of file %s", this.mSrc)); //$NON-NLS-1$
+        }
+
+        // Check that the file exists
+        if (!this.mSrc.exists()) {
+            if (isTrace()) {
+                Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$
+            }
+            throw new NoSuchFileOrDirectory(this.mSrc.getAbsolutePath());
+        }
+
+        CHECKSUMS checksum = CHECKSUMS.MD5;
+        try {
+            if (this.mAsyncResultListener != null) {
+                this.mAsyncResultListener.onAsyncStart();
+            }
+
+            // Calculate digests
+            calculateDigest(checksum);
+            checksum = CHECKSUMS.SHA1;
+            calculateDigest(checksum);
+
+            if (this.mAsyncResultListener != null) {
+                this.mAsyncResultListener.onAsyncEnd(false);
+            }
+            if (this.mAsyncResultListener != null) {
+                this.mAsyncResultListener.onAsyncExitCode(0);
+            }
+
+            if (isTrace()) {
+                Log.v(TAG, "Result: OK"); //$NON-NLS-1$
+            }
+
+        } catch (InterruptedException ie) {
+            if (this.mAsyncResultListener != null) {
+                this.mAsyncResultListener.onAsyncEnd(true);
+            }
+            if (this.mAsyncResultListener != null) {
+                this.mAsyncResultListener.onAsyncExitCode(143);
+            }
+
+            if (isTrace()) {
+                Log.v(TAG, "Result: CANCELLED"); //$NON-NLS-1$
+            }
+
+        } catch (Exception e) {
+            Log.e(TAG,
+                    String.format(
+                            "Fail to calculate %s checksum of file %s", //$NON-NLS-1$
+                            checksum.name(),
+                            this.mSrc.getAbsolutePath()),
+                    e);
+            if (this.mAsyncResultListener != null) {
+                this.mAsyncResultListener.onException(e);
+            }
+            if (isTrace()) {
+                Log.v(TAG, "Result: FAIL"); //$NON-NLS-1$
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isCancelled() {
+        synchronized (this.mSync) {
+            return this.mCancelled;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean cancel() {
+        try {
+            synchronized (this.mSync) {
+                this.mCancelled = true;
+            }
+        } catch (Throwable _throw) {/**NON BLOCK**/}
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean end() {
+        return cancel();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setOnEndListener(OnEndListener onEndListener) {
+        //Ignore. Java console don't use this
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setOnCancelListener(OnCancelListener onCancelListener) {
+        //Ignore. Java console don't use this
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String[] getResult() {
+        return this.mChecksums;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getChecksum(CHECKSUMS checksum) {
+        return getResult()[checksum.ordinal()];
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isCancellable() {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public AsyncResultListener getAsyncResultListener() {
+        return this.mAsyncResultListener;
+    }
+
+    /**
+     * Method that calculate a digest of the file for the source file
+     *
+     * @param type The type of digest to obtain
+     * @throws InterruptedException If the operation was cancelled
+     * @throws Exception If an error occurs
+     */
+    private void calculateDigest(CHECKSUMS type) throws InterruptedException, Exception {
+
+        InputStream is = null;
+        try {
+            MessageDigest md = MessageDigest.getInstance(type.name());
+            is = new FileInputStream(this.mSrc);
+
+            // Start digesting
+            byte[] data = new byte[getBufferSize()];
+            int read = 0;
+            while ((read = is.read(data, 0, getBufferSize())) != -1) {
+                checkCancelled();
+                md.update(data, 0, read);
+            }
+            checkCancelled();
+
+            // Finally digest
+            this.mChecksums[type.ordinal()] = HexDump.toHexString(md.digest());
+            checkCancelled();
+            if (this.mAsyncResultListener != null) {
+                this.mAsyncResultListener.onAsyncEnd(this.mCancelled);
+            }
+
+        } finally {
+            try {
+                if (is != null) {
+                    is.close();
+                }
+            } catch (Exception e) {/**NON BLOCK**/}
+        }
+    }
+
+    /**
+     * Checks if the operation was cancelled
+     *
+     * @throws InterruptedException If the operation was cancelled
+     */
+    private void checkCancelled() throws InterruptedException {
+        synchronized (this.mSync) {
+            if (this.mCancelled) {
+                throw new InterruptedException();
+            }
+        }
+    }
+}
index 1f680e4..94856ba 100644 (file)
@@ -20,6 +20,7 @@ import com.cyanogenmod.filemanager.R;
 import com.cyanogenmod.filemanager.commands.AsyncResultListener;
 import com.cyanogenmod.filemanager.commands.ChangeOwnerExecutable;
 import com.cyanogenmod.filemanager.commands.ChangePermissionsExecutable;
+import com.cyanogenmod.filemanager.commands.ChecksumExecutable;
 import com.cyanogenmod.filemanager.commands.CompressExecutable;
 import com.cyanogenmod.filemanager.commands.CopyExecutable;
 import com.cyanogenmod.filemanager.commands.CreateDirExecutable;
@@ -390,4 +391,14 @@ public class JavaExecutableCreator implements ExecutableCreator {
         throw new CommandNotFoundException("Not implemented"); //$NON-NLS-1$
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ChecksumExecutable createChecksumExecutable(
+            String src, AsyncResultListener asyncResultListener)
+            throws CommandNotFoundException {
+        return new ChecksumCommand(src, asyncResultListener);
+    }
+
 }
diff --git a/src/com/cyanogenmod/filemanager/commands/shell/ChecksumCommand.java b/src/com/cyanogenmod/filemanager/commands/shell/ChecksumCommand.java
new file mode 100644 (file)
index 0000000..97fbea3
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2012 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.cyanogenmod.filemanager.commands.shell;
+
+import com.cyanogenmod.filemanager.commands.AsyncResultListener;
+import com.cyanogenmod.filemanager.commands.ChecksumExecutable;
+import com.cyanogenmod.filemanager.commands.SIGNAL;
+import com.cyanogenmod.filemanager.console.CommandNotFoundException;
+import com.cyanogenmod.filemanager.console.ExecutionException;
+import com.cyanogenmod.filemanager.console.InsufficientPermissionsException;
+
+import java.io.File;
+
+/**
+ * A class for calculate MD5 and SHA-1 checksums of a file system object.<br />
+ * <br />
+ * Partial results are returned in order (MD5 -> SHA1)
+ *
+ * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?md5sum"}
+ * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?sha1sum"}
+ * @see com.cyanogenmod.filemanager.commands.ChecksumExecutable.CHECKSUMS
+ */
+public class ChecksumCommand extends AsyncResultProgram implements ChecksumExecutable {
+
+    private static final String ID = "checksum";  //$NON-NLS-1$
+
+    private final String mName;
+    private final String[] mChecksums;
+    private int mChecksumsCounter;
+    private String mPartial;
+
+    /**
+     * Constructor of <code>ChecksumCommand</code>.
+     *
+     * @param src The source file
+     * @param asyncResultListener The partial result listener
+     * @throws InvalidCommandDefinitionException If the command has an invalid definition
+     */
+    public ChecksumCommand(String src, AsyncResultListener asyncResultListener)
+            throws InvalidCommandDefinitionException {
+        super(ID, asyncResultListener, src);
+        this.mChecksums = new String[]{null, null};
+        this.mName = new File(src).getName();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onStartParsePartialResult() {
+        this.mChecksums[0] = null;
+        this.mChecksums[1] = null;
+        this.mChecksumsCounter = 0;
+        this.mPartial = ""; //$NON-NLS-1$
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onEndParsePartialResult(boolean cancelled) {
+        // Send the last partial data
+        if (this.mPartial != null && this.mPartial.length() > 0) {
+            if (getAsyncResultListener() != null) {
+                String data = processPartialResult(this.mPartial);
+                if (data != null) {
+                    getAsyncResultListener().onPartialResult(data);
+                }
+            }
+        }
+        this.mPartial = ""; //$NON-NLS-1$
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onParsePartialResult(final String partialIn) {
+        if (partialIn == null || partialIn.length() ==0) return;
+        boolean endsWithNewLine = partialIn.endsWith("\n"); //$NON-NLS-1$
+        String[] lines = partialIn.split("\n"); //$NON-NLS-1$
+
+        // Append the pending data to the first line
+        lines[0] = this.mPartial + lines[0];
+
+        // Return all the lines, except the last
+        for (int i = 0; i < lines.length-1; i++) {
+            if (getAsyncResultListener() != null) {
+                String data = processPartialResult(lines[i]);
+                if (data != null) {
+                    getAsyncResultListener().onPartialResult(data);
+                }
+            }
+        }
+
+        // Return the last line?
+        if (endsWithNewLine) {
+            if (getAsyncResultListener() != null) {
+                String data = processPartialResult(lines[lines.length-1]);
+                if (data != null) {
+                    getAsyncResultListener().onPartialResult(data);
+                }
+            }
+            this.mPartial = ""; //$NON-NLS-1$
+        } else {
+            // Save the partial for next calls
+            this.mPartial = lines[lines.length-1];
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onParseErrorPartialResult(String partialErr) {/**NON BLOCK**/}
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public SIGNAL onRequestEnd() {
+        try {
+            if (this.getProgramListener().getOutputStream() != null) {
+                this.getProgramListener().getOutputStream().flush();
+            }
+        } catch (Exception ex) {/**NON BLOCK**/}
+        try {
+            Thread.yield();
+        } catch (Exception ex) {/**NON BLOCK**/}
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String[] getResult() {
+        return this.mChecksums;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getChecksum(CHECKSUMS checksum) {
+        return getResult()[checksum.ordinal()];
+    }
+
+    /**
+     * Method that processes a line to determine if it's a valid partial result
+     *
+     * @param line The line to process
+     * @return String The processed line
+     */
+    private String processPartialResult(String line) {
+        // MD5 and SHA-1 return both the digest and the name of the file
+        // 4c044b884cf2ff3839713da0e81dced19f099b09  boot.zip
+        int pos = line.indexOf(" "); //$NON-NLS-1$
+        if (line.endsWith(this.mName) && pos != -1) {
+            String digest = line.substring(0, pos).trim();
+            if (this.mChecksumsCounter < this.mChecksums.length) {
+                this.mChecksums[this.mChecksumsCounter] = digest;
+            }
+            this.mChecksumsCounter++;
+            return digest;
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void checkExitCode(int exitCode)
+            throws InsufficientPermissionsException, CommandNotFoundException, ExecutionException {
+        //Ignore exit code 143 (cancelled)
+        //Ignore exit code 137 (kill -9)
+        if (exitCode != 0 && exitCode != 143 && exitCode != 137) {
+            throw new ExecutionException(
+                        "exitcode != 0 && != 143 && != 137"); //$NON-NLS-1$
+        }
+    }
+
+}
index 339ca09..ca304f5 100644 (file)
@@ -19,6 +19,7 @@ package com.cyanogenmod.filemanager.commands.shell;
 import com.cyanogenmod.filemanager.commands.AsyncResultListener;
 import com.cyanogenmod.filemanager.commands.ChangeOwnerExecutable;
 import com.cyanogenmod.filemanager.commands.ChangePermissionsExecutable;
+import com.cyanogenmod.filemanager.commands.ChecksumExecutable;
 import com.cyanogenmod.filemanager.commands.CompressExecutable;
 import com.cyanogenmod.filemanager.commands.CopyExecutable;
 import com.cyanogenmod.filemanager.commands.CreateDirExecutable;
@@ -48,6 +49,8 @@ import com.cyanogenmod.filemanager.commands.SendSignalExecutable;
 import com.cyanogenmod.filemanager.commands.UncompressExecutable;
 import com.cyanogenmod.filemanager.commands.WriteExecutable;
 import com.cyanogenmod.filemanager.console.CommandNotFoundException;
+import com.cyanogenmod.filemanager.console.InsufficientPermissionsException;
+import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
 import com.cyanogenmod.filemanager.console.shell.ShellConsole;
 import com.cyanogenmod.filemanager.model.Group;
 import com.cyanogenmod.filemanager.model.MountPoint;
@@ -520,4 +523,19 @@ public class ShellExecutableCreator implements ExecutableCreator {
         }
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ChecksumExecutable createChecksumExecutable(
+            String src, AsyncResultListener asyncResultListener)
+            throws CommandNotFoundException, NoSuchFileOrDirectory,
+            InsufficientPermissionsException {
+        try {
+            return new ChecksumCommand(src, asyncResultListener);
+        } catch (InvalidCommandDefinitionException icdEx) {
+            throw new CommandNotFoundException("ChecksumCommand", icdEx); //$NON-NLS-1$
+        }
+    }
+
 }
index efb6627..8c20543 100644 (file)
@@ -38,6 +38,7 @@ import com.cyanogenmod.filemanager.adapters.TwoColumnsMenuListAdapter;
 import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener;
 import com.cyanogenmod.filemanager.listeners.OnSelectionListener;
 import com.cyanogenmod.filemanager.model.FileSystemObject;
+import com.cyanogenmod.filemanager.model.Symlink;
 import com.cyanogenmod.filemanager.model.SystemFile;
 import com.cyanogenmod.filemanager.preferences.AccessMode;
 import com.cyanogenmod.filemanager.ui.ThemeManager;
@@ -394,6 +395,11 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen
                 IntentsActionPolicy.createShortcut(this.mContext, this.mFso);
                 break;
 
+            //- Compute checksum
+            case R.id.mnu_actions_compute_checksum:
+                InfoActionPolicy.showComputeChecksumDialog(this.mContext, this.mFso);
+                break;
+
             //- Properties
             case R.id.mnu_actions_properties:
             case R.id.mnu_actions_properties_current_folder:
@@ -614,6 +620,11 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen
             if (category.compareTo(MimeTypeCategory.EXEC) != 0) {
                 menu.removeItem(R.id.mnu_actions_execute);
             }
+
+            //- Checksum (only supported for files)
+            if (FileHelper.isDirectory(this.mFso) || this.mFso instanceof Symlink) {
+                menu.removeItem(R.id.mnu_actions_compute_checksum);
+            }
         }
 
         //- Add to bookmarks -> Only directories
diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/ComputeChecksumDialog.java b/src/com/cyanogenmod/filemanager/ui/dialogs/ComputeChecksumDialog.java
new file mode 100644 (file)
index 0000000..f7b34bb
--- /dev/null
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2012 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.cyanogenmod.filemanager.ui.dialogs;
+
+import android.app.AlertDialog;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.cyanogenmod.filemanager.R;
+import com.cyanogenmod.filemanager.commands.AsyncResultExecutable;
+import com.cyanogenmod.filemanager.commands.AsyncResultListener;
+import com.cyanogenmod.filemanager.model.FileSystemObject;
+import com.cyanogenmod.filemanager.ui.ThemeManager;
+import com.cyanogenmod.filemanager.ui.ThemeManager.Theme;
+import com.cyanogenmod.filemanager.util.CommandHelper;
+import com.cyanogenmod.filemanager.util.DialogHelper;
+import com.cyanogenmod.filemanager.util.ExceptionUtil;
+/**
+ * A class that wraps a dialog for computing the checksums of a {@link FileSystemObject}
+ */
+public class ComputeChecksumDialog implements
+    DialogInterface.OnClickListener, View.OnClickListener, AsyncResultListener {
+
+    /**
+     * @hide
+     */
+    final Context mContext;
+    /**
+     * @hide
+     */
+    final FileSystemObject mFso;
+    private final Handler mHandler;
+    /**
+     * @hide
+     */
+    final AlertDialog mDialog;
+
+    // For cancel the operation
+    /**
+     * @hide
+     */
+    AsyncResultExecutable mCmd;
+    /**
+     * @hide
+     */
+    boolean mFinished;
+
+    /**
+     * @hide
+     */
+    EditText[] mChecksums = new EditText[2];
+
+    /**
+     * @hide
+     */
+    int mComputeStatus;
+
+    private final ClipboardManager mClipboardMgr;
+
+    /**
+     * Constructor of <code>ComputeChecksumDialog</code>.
+     *
+     * @param context The current context
+     * @param fso The file system object to execute
+     */
+    public ComputeChecksumDialog(final Context context, final FileSystemObject fso) {
+        super();
+
+        // Save properties
+        this.mContext = context;
+        this.mFso = fso;
+        this.mHandler = new Handler();
+        this.mComputeStatus = 0;
+
+        this.mClipboardMgr =
+                (ClipboardManager)this.mContext.getSystemService(Context.CLIPBOARD_SERVICE);
+
+        //Create the layout
+        LayoutInflater li =
+                (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        ViewGroup layout = (ViewGroup)li.inflate(R.layout.compute_checksum_dialog, null);
+        TextView tvFileName = (TextView)layout.findViewById(R.id.checksum_filename);
+        tvFileName.setText(fso.getFullPath());
+        this.mChecksums[0] = (EditText)layout.findViewById(R.id.checksum_md5);
+        this.mChecksums[1] = (EditText)layout.findViewById(R.id.checksum_sha1);
+        View btMD5 = layout.findViewById(R.id.bt_md5_clipboard);
+        btMD5.setOnClickListener(this);
+        View btSHA1 = layout.findViewById(R.id.bt_sha1_clipboard);
+        btSHA1.setOnClickListener(this);
+
+        // Apply the theme
+        applyTheme(context, layout);
+
+        //Create the dialog
+        String title = context.getString(R.string.compute_checksum_title);
+        this.mDialog = DialogHelper.createDialog(
+                                        context,
+                                        0,
+                                        title,
+                                        layout);
+        this.mDialog.setButton(
+                DialogInterface.BUTTON_NEUTRAL, context.getString(android.R.string.cancel), this);
+
+        // Start checksum compute
+        try {
+            this.mCmd = CommandHelper.checksum(context, fso.getFullPath(), this, null);
+        } catch (Exception e) {
+            ExceptionUtil.translateException(context, e);
+        }
+    }
+
+    /**
+     * Method that shows the dialog.
+     */
+    public void show() {
+        DialogHelper.delegateDialogShow(this.mContext, this.mDialog);
+    }
+
+    /**
+     * Method that dismiss the dialog.
+     */
+    public void dismiss() {
+        this.mDialog.dismiss();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        switch (which) {
+            case DialogInterface.BUTTON_NEUTRAL:
+                // Cancel the program?
+                try {
+                    if (this.mCmd != null && !this.mFinished) {
+                        if (this.mCmd.isCancellable() && !this.mCmd.isCancelled()) {
+                            this.mCmd.cancel();
+                        }
+                    }
+                } catch (Exception e) {/**NON BLOCK**/}
+                this.mDialog.dismiss();
+                break;
+
+            default:
+                break;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onClick(View v) {
+        String digest = ""; //$NON-NLS-1$
+        String label = ""; //$NON-NLS-1$
+        switch (v.getId()) {
+            case R.id.bt_md5_clipboard:
+                digest = this.mChecksums[0].getText().toString();
+                label = String.format("MD5 Checksum - %s", this.mFso.getFullPath()); //$NON-NLS-1$
+                break;
+            case R.id.bt_sha1_clipboard:
+                digest = this.mChecksums[1].getText().toString();
+                label = String.format("SHA-1 Checksum - %s", this.mFso.getFullPath()); //$NON-NLS-1$
+                break;
+
+            default:
+                break;
+        }
+
+        // Copy text to clipboard
+        if (this.mClipboardMgr != null) {
+            ClipData clip =ClipData.newPlainText(label, digest);
+            this.mClipboardMgr.setPrimaryClip(clip);
+            DialogHelper.showToast(this.mContext, R.string.copy_text_msg, Toast.LENGTH_SHORT);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAsyncStart() {
+        /** NON BLOCK **/
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAsyncEnd(boolean cancelled) {
+        /** NON BLOCK **/
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAsyncExitCode(int exitCode) {
+        if (exitCode != 0) {
+            this.mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    int cc = ComputeChecksumDialog.this.mChecksums.length;
+                    for (int i = ComputeChecksumDialog.this.mComputeStatus; i < cc; i++) {
+                        ComputeChecksumDialog.this.mChecksums[i].setText(R.string.error_message);
+                    }
+                }
+            });
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onPartialResult(final Object result) {
+        this.mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                setChecksum(String.valueOf(result));
+            }
+        });
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onException(Exception cause) {
+        ExceptionUtil.translateException(this.mContext, cause, false, false);
+    }
+
+    /**
+     * Method that attach the checksum result to the view
+     *
+     * @param digest The digest value
+     * @hide
+     */
+    synchronized void setChecksum(String digest) {
+        this.mChecksums[this.mComputeStatus].setText(digest);
+        this.mComputeStatus++;
+    }
+
+    /**
+     * Method that applies the current theme to the dialog
+     *
+     * @param ctx The current context
+     * @param root The root view
+     */
+    private void applyTheme(Context ctx, ViewGroup root) {
+        // Apply the current theme
+        Theme theme = ThemeManager.getCurrentTheme(ctx);
+        theme.setBackgroundDrawable(ctx, root, "background_drawable"); //$NON-NLS-1$
+        View v = root.findViewById(R.id.checksum_filename_label);
+        theme.setTextColor(ctx, (TextView)v, "text_color"); //$NON-NLS-1$
+        v = root.findViewById(R.id.checksum_filename);
+        theme.setTextColor(ctx, (TextView)v, "text_color"); //$NON-NLS-1$
+        v = root.findViewById(R.id.checksum_md5_label);
+        theme.setTextColor(ctx, (TextView)v, "text_color"); //$NON-NLS-1$
+        theme.setBackgroundColor(ctx, this.mChecksums[0], "console_bg_color"); //$NON-NLS-1$
+        theme.setTextColor(ctx, this.mChecksums[0], "console_fg_color"); //$NON-NLS-1$
+        v = root.findViewById(R.id.checksum_sha1_label);
+        theme.setTextColor(ctx, (TextView)v, "text_color"); //$NON-NLS-1$
+        theme.setBackgroundColor(ctx, this.mChecksums[1], "console_bg_color"); //$NON-NLS-1$
+        theme.setTextColor(ctx, this.mChecksums[1], "console_fg_color"); //$NON-NLS-1$
+        v = root.findViewById(R.id.bt_md5_clipboard);
+        theme.setImageDrawable(ctx, (ImageView)v, "ic_copy_drawable"); //$NON-NLS-1$
+        v = root.findViewById(R.id.bt_sha1_clipboard);
+        theme.setImageDrawable(ctx, (ImageView)v, "ic_copy_drawable"); //$NON-NLS-1$
+    }
+}
index 5bc8ed4..b0a7c3a 100644 (file)
@@ -146,29 +146,17 @@ public class ExecutionDialog implements DialogInterface.OnClickListener {
         LayoutInflater li =
                 (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         ViewGroup layout = (ViewGroup)li.inflate(R.layout.execution_dialog, null);
-        View tvScriptNameLabel = layout.findViewById(R.id.execution_script_name_label);
         TextView tvScriptName = (TextView)layout.findViewById(R.id.execution_script_name);
         tvScriptName.setText(fso.getFullPath());
-        View tvTimeLabel = layout.findViewById(R.id.execution_time_label);
         this.mTvTime = (TextView)layout.findViewById(R.id.execution_time);
         this.mTvTime.setText("-"); //$NON-NLS-1$
-        View tvExitCodeLabel = layout.findViewById(R.id.execution_exitcode_label);
         this.mTvExitCode = (TextView)layout.findViewById(R.id.execution_exitcode);
         this.mTvExitCode.setText("-"); //$NON-NLS-1$
         this.mTvOutput = (TextView)layout.findViewById(R.id.execution_output);
         this.mTvOutput.setMovementMethod(new ScrollingMovementMethod());
 
-        // Apply the current theme
-        Theme theme = ThemeManager.getCurrentTheme(context);
-        theme.setBackgroundDrawable(context, layout, "background_drawable"); //$NON-NLS-1$
-        theme.setTextColor(context, (TextView)tvScriptNameLabel, "text_color"); //$NON-NLS-1$
-        theme.setTextColor(context, tvScriptName, "text_color"); //$NON-NLS-1$
-        theme.setTextColor(context, (TextView)tvTimeLabel, "text_color"); //$NON-NLS-1$
-        theme.setTextColor(context, this.mTvTime, "text_color"); //$NON-NLS-1$
-        theme.setTextColor(context, (TextView)tvExitCodeLabel, "text_color"); //$NON-NLS-1$
-        theme.setTextColor(context, this.mTvExitCode, "text_color"); //$NON-NLS-1$
-        theme.setBackgroundColor(context, this.mTvOutput, "console_bg_color"); //$NON-NLS-1$
-        theme.setTextColor(context, this.mTvOutput, "console_fg_color"); //$NON-NLS-1$
+        // Apply the theme
+        applyTheme(context, layout);
 
         //Create the dialog
         String title = context.getString(R.string.execution_console_title);
@@ -369,4 +357,28 @@ public class ExecutionDialog implements DialogInterface.OnClickListener {
         }
     }
 
+    /**
+     * Method that applies the current theme to the dialog
+     *
+     * @param ctx The current context
+     * @param root The root view
+     */
+    private void applyTheme(Context ctx, ViewGroup root) {
+        // Apply the current theme
+        Theme theme = ThemeManager.getCurrentTheme(ctx);
+        theme.setBackgroundDrawable(ctx, root, "background_drawable"); //$NON-NLS-1$
+        View v = root.findViewById(R.id.execution_time_label);
+        theme.setTextColor(ctx, (TextView)v, "text_color"); //$NON-NLS-1$
+        v = root.findViewById(R.id.execution_script_name);
+        theme.setTextColor(ctx, (TextView)v, "text_color"); //$NON-NLS-1$
+        v = root.findViewById(R.id.execution_time_label);
+        theme.setTextColor(ctx, (TextView)v, "text_color"); //$NON-NLS-1$
+        theme.setTextColor(ctx, this.mTvTime, "text_color"); //$NON-NLS-1$
+        v = root.findViewById(R.id.execution_exitcode_label);
+        theme.setTextColor(ctx, (TextView)v, "text_color"); //$NON-NLS-1$
+        theme.setTextColor(ctx, this.mTvExitCode, "text_color"); //$NON-NLS-1$
+        theme.setBackgroundColor(ctx, this.mTvOutput, "console_bg_color"); //$NON-NLS-1$
+        theme.setTextColor(ctx, this.mTvOutput, "console_fg_color"); //$NON-NLS-1$
+    }
+
 }
index ef4639b..d35eddb 100644 (file)
@@ -22,6 +22,7 @@ import android.widget.Toast;
 
 import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener;
 import com.cyanogenmod.filemanager.model.FileSystemObject;
+import com.cyanogenmod.filemanager.ui.dialogs.ComputeChecksumDialog;
 import com.cyanogenmod.filemanager.ui.dialogs.FsoPropertiesDialog;
 import com.cyanogenmod.filemanager.util.DialogHelper;
 
@@ -68,4 +69,17 @@ public final class InfoActionPolicy extends ActionsPolicy {
         dialog.show();
     }
 
+    /**
+     * Method that show a new dialog for compute checksum of a {@link FileSystemObject}.
+     *
+     * @param ctx The current context
+     * @param fso The file system object
+     * of the {@link FileSystemObject} were changed (optional)
+     */
+    public static void showComputeChecksumDialog(
+            final Context ctx, final FileSystemObject fso) {
+        //Show a the filesystem info dialog
+        final ComputeChecksumDialog dialog = new ComputeChecksumDialog(ctx, fso);
+        dialog.show();
+    }
 }
\ No newline at end of file
index 85b6a03..2a5925c 100644 (file)
@@ -21,6 +21,7 @@ import android.content.Context;
 import com.cyanogenmod.filemanager.commands.AsyncResultListener;
 import com.cyanogenmod.filemanager.commands.ChangeOwnerExecutable;
 import com.cyanogenmod.filemanager.commands.ChangePermissionsExecutable;
+import com.cyanogenmod.filemanager.commands.ChecksumExecutable;
 import com.cyanogenmod.filemanager.commands.CompressExecutable;
 import com.cyanogenmod.filemanager.commands.CopyExecutable;
 import com.cyanogenmod.filemanager.commands.CreateDirExecutable;
@@ -1435,6 +1436,41 @@ public final class CommandHelper {
     }
 
     /**
+     * Method that calculates the checksum of a file system object.
+     *
+     * @param context The current context (needed if console == null)
+     * @param src The source file
+     * @param asyncResultListener The partial result listener
+     * @param console The console in which execute the program.
+     * <code>null</code> to attach to the default console
+     * @return WriteExecutable The command executed in background
+     * @throws FileNotFoundException If the initial directory not exists
+     * @throws IOException If initial directory couldn't be checked
+     * @throws InvalidCommandDefinitionException If the command has an invalid definition
+     * @throws NoSuchFileOrDirectory If the file or directory was not found
+     * @throws ConsoleAllocException If the console can't be allocated
+     * @throws InsufficientPermissionsException If an operation requires elevated permissions
+     * @throws CommandNotFoundException If the command was not found
+     * @throws OperationTimeoutException If the operation exceeded the maximum time of wait
+     * @throws ExecutionException If the operation returns a invalid exit code
+     * @see WriteExecutable
+     */
+    public static ChecksumExecutable checksum(
+            Context context, String src,
+            AsyncResultListener asyncResultListener, Console console)
+            throws FileNotFoundException, IOException, ConsoleAllocException,
+            NoSuchFileOrDirectory, InsufficientPermissionsException,
+            CommandNotFoundException, OperationTimeoutException,
+            ExecutionException, InvalidCommandDefinitionException {
+        Console c = ensureConsole(context, console);
+        ChecksumExecutable executable =
+                c.getExecutableFactory().newCreator().
+                    createChecksumExecutable(src, asyncResultListener);
+        execute(context, executable, c);
+        return executable;
+    }
+
+    /**
      * Method that re-execute the command.
      *
      * @param context The current context (needed if console == null)
diff --git a/tests/src/com/cyanogenmod/filemanager/commands/shell/ChecksumCommandTest.java b/tests/src/com/cyanogenmod/filemanager/commands/shell/ChecksumCommandTest.java
new file mode 100644 (file)
index 0000000..791a00e
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2012 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.cyanogenmod.filemanager.commands.shell;
+
+import android.os.Environment;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.cyanogenmod.filemanager.commands.AsyncResultListener;
+import com.cyanogenmod.filemanager.commands.ChecksumExecutable;
+import com.cyanogenmod.filemanager.commands.ChecksumExecutable.CHECKSUMS;
+import com.cyanogenmod.filemanager.util.CommandHelper;
+
+/**
+ * A class for testing checksum command.
+ *
+ * @see ChecksumCommand
+ */
+public class ChecksumCommandTest extends AbstractConsoleTest {
+
+    private static final String TEST_FILE =
+            Environment.getRootDirectory().getAbsolutePath() + "/fonts/Roboto-Bold.ttf"; //$NON-NLS-1$
+
+    private static final String MD5_SUM = "0a15e86bdff7da5886fe6535b50d9988"; //$NON-NLS-1$
+    private static final String SHA1_SUM = "624735f02422f13e50ccf466f0d29edda05adb36"; //$NON-NLS-1$
+
+    /**
+     * @hide
+     */
+    final Object mSync = new Object();
+    /**
+     * @hide
+     */
+    boolean mNormalEnd;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isRootConsoleNeeded() {
+        return true;
+    }
+
+    /**
+     * Method that performs a checksum test
+     *
+     * @throws Exception If an exception occurs while executing the test
+     */
+    @SmallTest
+    @SuppressWarnings("null")
+    public void testChecksums() throws Exception {
+        ChecksumExecutable cmd =
+                CommandHelper.checksum(getContext(),
+                        TEST_FILE, new AsyncResultListener() {
+                            @Override
+                            public void onAsyncStart() {
+                                /**NON BLOCK**/
+                            }
+                            @Override
+                            public void onAsyncEnd(boolean cancelled) {
+                                synchronized (ChecksumCommandTest.this.mSync) {
+                                    ChecksumCommandTest.this.mNormalEnd = true;
+                                    ChecksumCommandTest.this.mSync.notify();
+                                }
+                            }
+                            @Override
+                            public void onAsyncExitCode(int exitCode) {
+                                /**NON BLOCK**/
+                            }
+                            @Override
+                            public void onException(Exception cause) {
+                                fail(String.valueOf(cause));
+                            }
+                            @Override
+                            public void onPartialResult(Object results) {
+                                /**NON BLOCK**/
+                            }
+                        }, getConsole());
+
+        synchronized (ChecksumCommandTest.this.mSync) {
+            ChecksumCommandTest.this.mSync.wait(15000L);
+        }
+        try {
+            if (!this.mNormalEnd && cmd != null && cmd.isCancellable() && !cmd.isCancelled()) {
+                cmd.cancel();
+            }
+        } catch (Exception e) {/**NON BLOCK**/}
+        assertNotNull("md5==null", cmd.getChecksum(CHECKSUMS.MD5)); //$NON-NLS-1$
+        assertNotNull("sha1==null", cmd.getChecksum(CHECKSUMS.SHA1)); //$NON-NLS-1$
+        assertEquals("md5sum fails", MD5_SUM, cmd.getChecksum(CHECKSUMS.MD5)); //$NON-NLS-1$
+        assertEquals("sha1sum fails", SHA1_SUM, cmd.getChecksum(CHECKSUMS.SHA1)); //$NON-NLS-1$
+    }
+
+}
diff --git a/themes/res/drawable-hdpi/ic_holo_dark_copy.png b/themes/res/drawable-hdpi/ic_holo_dark_copy.png
new file mode 100644 (file)
index 0000000..72c6bc6
Binary files /dev/null and b/themes/res/drawable-hdpi/ic_holo_dark_copy.png differ
diff --git a/themes/res/drawable-mdpi/ic_holo_dark_copy.png b/themes/res/drawable-mdpi/ic_holo_dark_copy.png
new file mode 100644 (file)
index 0000000..d93968e
Binary files /dev/null and b/themes/res/drawable-mdpi/ic_holo_dark_copy.png differ
diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_copy.png b/themes/res/drawable-xhdpi/ic_holo_dark_copy.png
new file mode 100644 (file)
index 0000000..04e290d
Binary files /dev/null and b/themes/res/drawable-xhdpi/ic_holo_dark_copy.png differ
index c67474e..f1944d8 100644 (file)
   <drawable name="dark_ic_usb_drawable">@drawable/ic_holo_dark_usb</drawable>
   <drawable name="dark_ic_user_defined_bookmark_drawable">@drawable/ic_holo_dark_user_defined_bookmark</drawable>
   <!--<drawable name="dark_ic_history_search_drawable">@null</drawable> -->
+  <drawable name="dark_ic_copy_drawable">@drawable/ic_holo_dark_copy</drawable>
 
   <!-- Disk usage graph -->
   <color name="dark_disk_usage_total_color">#7ecccccc</color>