Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Drag and drop to install apk files from your computer #133

Closed
wants to merge 23 commits into from

Conversation

AdoPi
Copy link
Contributor

@AdoPi AdoPi commented Apr 28, 2018

I have just added a little feature to install apk files by using drag and drop.

So, just drag and drop an apk file and wait for its installation to complete.

@AdoPi AdoPi changed the title [Feature] Drag and drop to install apk files from your computer to Android [Feature] Drag and drop to install apk files from your computer Apr 28, 2018
@rom1v
Copy link
Collaborator

rom1v commented Apr 30, 2018

Oh, I saw the title of the PR in my mails, and thought it was a feature request, I didn't realize it was a pull request 😄

That's cool, I didn't know about SDL_DROPFILE 👍 Thank you.

However, currently, the installation blocks the main thread (so the UI is frozen), it should be executed in a separate thread instead.

(Besides, since it is a new feature, it should target dev branch)

@AdoPi
Copy link
Contributor Author

AdoPi commented Apr 30, 2018

Thanks :)
I am going to work on a version which uses separate thread so.
Sorry I don't know how to change the target of an existing pull request, do you know how can I do that?

@AdoPi
Copy link
Contributor Author

AdoPi commented Apr 30, 2018

dc68d99 commit is a quick fix.

Now, the UI should not be frozen.

@rom1v
Copy link
Collaborator

rom1v commented Apr 30, 2018

Now, the UI should not be frozen.

That's true. However, each "adb install" child process will not be reaped, and will stay zombies until the scrcpy process terminates.

Instead, I suggest to create a thread, keep a reference to it so that you can join it when scrcpy terminates. A priori, it should be similar to what decoder.c does.

By the way, all this APK installation stuff (what you currently implement in server.c) could be managed in a separate file (installapk.c or whatever), so that the implementation details¹ are hidden from the caller.

What do you think?

¹These implementation details include decisions about:

  • how to manage the separate thread? create a new one each time? reuse it?
  • what happens if we start a new installation before the previous one is complete? (serialized on the same thread, in parallel…)
  • what happens if we close scrcpy while an installation is running?

@AdoPi
Copy link
Contributor Author

AdoPi commented Apr 30, 2018

how to manage the separate thread? create a new one each time? reuse it?
what happens if we start a new installation before the previous one is complete? (serialized on the same thread, in parallel…)

My idea was to make an executor thread which takes commands from a command queue and execute them (thus serialized on the same thread).
The latest commit was just here to have something which works while I'm working on a better design which may take some time 👎

what happens if we close scrcpy while an installation is running?

It depends on adb, I think we can expect the same behaviour as when we disconnect the phone while adb is installing something on it.

@rom1v
Copy link
Collaborator

rom1v commented Apr 30, 2018

My idea was to make an executor thread which takes commands from a command queue and execute them (thus serialized on the same thread).

👍

The executor thread could be created only on the first time it is required (APK installations will probably be rare compared to scrcpy executions).

It depends on adb, I think we can expect the same behaviour as when we disconnect the phone while adb is installing something on it.

Yes, killing the process (like server.c does) is ok for me 👍

@AdoPi
Copy link
Contributor Author

AdoPi commented May 3, 2018

The latest commit should add this drag&drop feature in a more proper way.
What do you think? :)

If someday someone needs to execute other commands apart from "adb install" , it would be nice to have a command queue instead of a filename queue to be more generic.

@rom1v
Copy link
Collaborator

rom1v commented May 3, 2018

Thank you 👍 I will review this when I have some time, hopefully this week-end.

Copy link
Collaborator

@rom1v rom1v left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work, it works well without freezing the UI 👍

You reused the structure of existing code, so this makes the new code consistent, that's great. 😄

I have two main requests before merging it:

  • the apk queue should not reserve space for MAX_FILENAME_SIZE (that's wasteful and unsafe if the filename is more than MAX_FILENAME_SIZE). Instead it should just store pointers to allocated strings (and free them as necessary) IMO.
  • the lazy initialization should be internal to apkinstaller.h. IMO the caller should always initially call installer_init() as if the installer queue/thread were not lazy initialized (it's an implementation detail, we should be able to change the lazy-initialization behavior without the caller being aware of it). In that case, installer_init() would just initialize the fiels, but not create the thread, etc. But if you want, after all, I think it's ok not to implement lazy initialization (this is probably a premature optimization that I suggested).

In addition, some suggestions:

  • pass -r argument to adb install to support upgrades: adb install -r file.apk
  • use the same name for the file and the struct (either installer.h/struct installer, either apkinstaller.h/struct apk_installer)

I also commented directly on the diff for tiny details.

}

void apk_queue_destroy(struct apk_queue *queue) {
// do nothing as we are on the stack
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is nothing to do, but we are not on the stack 😉

SDL_DestroyMutex(installer->mutex);
apk_queue_destroy(&installer->queue);
SDL_free((void *) installer->serial);
installer->initialized = SDL_FALSE;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After destroy, the installer is considered in an undefined state, there is no need to set its variables.

void installer_join(struct installer *installer);

// install an apk
SDL_bool installer_push_apk(struct installer *installer, const char* filename);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name is confusing, we push a request for apk installation into the queue, we don't "push" an apk (it may be confused with adb push).

@@ -73,6 +73,11 @@ process_t adb_push(const char *serial, const char *local, const char *remote) {
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}

process_t adb_install(const char *serial, const char *local) {
const char *const adb_cmd[] = {"install", local};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{"install", "-r", local} to support upgrades 😉

app/src/scrcpy.c Outdated
@@ -102,6 +104,18 @@ static void event_loop(void) {
case SDL_MOUSEBUTTONUP:
input_manager_process_mouse_button(&input_manager, &event.button);
break;
case SDL_DROPFILE:

if (!installer.initialized) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, the fact that the installer is "lazy initialized" should not leak to the caller (it's an implementation detail).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool! I was wondering whether or not it was an implementation detail.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, it is possible not to hide the lazy initialization inside the installer: the installer could always be fully initialized on init, but the caller may call init only when necessary. In that case, the initialized flag would be on the caller side.

#define MAX_FILENAME_SIZE 1024


#include "apkinstaller.h"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😕

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry about that 👎

#define APK_INSTALLER_H

#define APK_QUEUE_SIZE 16
#define MAX_FILENAME_SIZE 1024
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be after the includes

#include "lockutil.h"
#include "log.h"
#include "command.h"
#include <string.h>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nitpicking)

For consistency:

#include "apkinstaller.h"

#include <string.h>
#include "command.h"
#include "lockutil.h"
#include "log.h"

(first the associated .h, then the sorted <> includes, then the sorted "" includes)

.current_process = PROCESS_NONE \
}


Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double empty line

#include "command.h"
#include <string.h>


Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double newline

@AdoPi
Copy link
Contributor Author

AdoPi commented May 5, 2018

Big thanks for your review and the time spent, I will work on it 👍

@AdoPi
Copy link
Contributor Author

AdoPi commented May 15, 2018

Thanks for the advice, it should be better now.
Keep me informed :)

@rom1v
Copy link
Collaborator

rom1v commented May 17, 2018

Thank you for you changes. I will probably not have time to review this week-end, but don't worry, I'm thinking about it 😉

@rom1v
Copy link
Collaborator

rom1v commented May 26, 2018

Hi,

Thank you for the feature.

I fixed some memory leaks and released the mutex while installing (I made me fix the controller for the same reason).

Here are the changes I suggest:

diff --git a/app/src/installer.c b/app/src/installer.c
index 95a15db..2bef19d 100644
--- a/app/src/installer.c
+++ b/app/src/installer.c
@@ -44,7 +44,8 @@ SDL_bool apk_queue_take(struct apk_queue *queue, char **apk) {
     if (apk_queue_is_empty(queue)) {
         return SDL_FALSE;
     }
-    *apk = SDL_strdup(queue->data[queue->tail]);
+    // transfer ownership
+    *apk = queue->data[queue->tail];
     queue->tail = (queue->tail + 1) % APK_QUEUE_SIZE;
     return SDL_TRUE;
 }
@@ -64,9 +65,14 @@ SDL_bool installer_init(struct installer *installer, const char *serial) {
         return SDL_FALSE;
     }
 
-    installer->serial = NULL;
     if (serial) {
         installer->serial = SDL_strdup(serial);
+        if (!installer->serial) {
+            LOGW("Cannot strdup serial");
+            return SDL_FALSE;
+        }
+    } else {
+        installer->serial = NULL;
     }
 
     // lazy initialization
@@ -104,35 +110,40 @@ SDL_bool installer_install_apk(struct installer *installer, const char *apk) {
     return res;
 }
 
-static SDL_bool process_install(struct installer *installer, const char *filename) {
-    LOGI("%s will be installed",filename);
-    process_t process = adb_install(installer->serial, filename);
-    installer->current_process = process;
-    return process_check_success(process, "adb install");
-}
-
 static int run_installer(void *data) {
     struct installer *installer = data;
 
-    char *current_apk;
-    mutex_lock(installer->mutex);
     for (;;) {
+        mutex_lock(installer->mutex);
         while (!installer->stopped && apk_queue_is_empty(&installer->queue)) {
             cond_wait(installer->event_cond, installer->mutex);
         }
         if (installer->stopped) {
             // stop immediately, do not process further events
+            mutex_unlock(installer->mutex);
             break;
         }
-        while (apk_queue_take(&installer->queue, &current_apk)) {
-            SDL_bool ok = process_install(installer,current_apk);
-            if (!ok) {
-                LOGD("Error during installation");
-            }
+        char *current_apk;
+#ifdef BUILD_DEBUG
+        bool non_empty = apk_queue_take(&installer->queue, &current_apk);
+        SDL_assert(non_empty);
+#else
+        apk_queue_take(&installer->queue, &current_apk);
+#endif
+
+        LOGI("Installing %s...", current_apk);
+        process_t process = adb_install(installer->serial, current_apk);
+        installer->current_process = process;
+
+        mutex_unlock(installer->mutex);
+
+        if (process_check_success(process, "adb install")) {
+            LOGI("%s installed successfully", current_apk);
+        } else {
+            LOGE("Failed to install %s", current_apk);
         }
+        SDL_free(current_apk);
     }
-    mutex_unlock(installer->mutex);
-    SDL_free(current_apk);
     return 0;
 }
 
@@ -152,7 +163,7 @@ void installer_stop(struct installer *installer) {
     mutex_lock(installer->mutex);
     installer->stopped = SDL_TRUE;
     cond_signal(installer->event_cond);
-    if (installer->current_process == PROCESS_NONE) {
+    if (installer->current_process != PROCESS_NONE) {
         if (!cmd_terminate(installer->current_process)) {
             LOGW("Cannot terminate install process");
         }
diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c
index 03bdc19..4e74bb9 100644
--- a/app/src/scrcpy.c
+++ b/app/src/scrcpy.c
@@ -152,10 +152,10 @@ SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 b
         goto finally_destroy_server;
     }
 
-    if (!installer_init(&installer,server.serial)) {
+    if (!installer_init(&installer, server.serial)) {
         ret = SDL_FALSE;
         server_stop(&server);
-        goto finally_destroy_installer;
+        goto finally_destroy_frames;
     }
 
     decoder_init(&decoder, &frames, device_socket);
@@ -165,7 +165,7 @@ SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 b
     if (!decoder_start(&decoder)) {
         ret = SDL_FALSE;
         server_stop(&server);
-        goto finally_destroy_frames;
+        goto finally_destroy_installer;
     }
 
     if (!controller_init(&controller, device_socket)) {

Please review 😉 If it's ok, I'll squash all these commits into one on dev.

I'll also add a workaround for Windows to support spaces in path:

diff --git a/app/src/command.c b/app/src/command.c
index 463eb42..6a7c1b9 100644
--- a/app/src/command.c
+++ b/app/src/command.c
@@ -74,6 +74,16 @@ process_t adb_push(const char *serial, const char *local, const char *remote) {
 }
 
 process_t adb_install(const char *serial, const char *local) {
+#ifdef __WINDOWS__
+    // Windows will parse the string, so the local name must be quoted (see sys/win/command.c)
+    size_t len = strlen(local);
+    char quoted[len + 3];
+    memcpy(&quoted[1], local, len);
+    quoted[0] = '"';
+    quoted[len + 1] = '"';
+    quoted[len + 2] = '\0';
+    local = quoted;
+#endif
     const char *const adb_cmd[] = {"install", "-r", local};
     return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
 }

@AdoPi
Copy link
Contributor Author

AdoPi commented May 28, 2018

Oh, I was not aware of the spaces problem for Windows path, that's cool to know (I don't really use Windows a lot).

Something in the mutex code was interpelling me too but I couldn't find why, nice job 💯
It seems to be fine for me!

rom1v pushed a commit that referenced this pull request May 28, 2018
@rom1v
Copy link
Collaborator

rom1v commented May 28, 2018

Commit e2a2973, and released in v1.2 👍 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants