diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2f4be72..a3f0fbd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -105,5 +105,8 @@ <activity android:name=".RemoteSettings" android:label="@string/remotesettings_activity"/> + <service + android:name=".services.clicknload.ClickNLoadService" + android:exported="true"/> </application> </manifest> diff --git a/app/src/main/java/org/pyload/android/client/pyLoad.java b/app/src/main/java/org/pyload/android/client/pyLoad.java index cd50b0e..ec83246 100644 --- a/app/src/main/java/org/pyload/android/client/pyLoad.java +++ b/app/src/main/java/org/pyload/android/client/pyLoad.java @@ -1,15 +1,21 @@ package org.pyload.android.client; -import java.io.File; -import java.io.FileInputStream; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Locale; - import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; import android.content.res.Configuration; -import android.view.*; +import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.net.Uri; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.widget.TabHost; + +import androidx.core.view.MenuItemCompat; import org.pyload.android.client.components.FragmentTabsPager; import org.pyload.android.client.dialogs.AccountDialog; @@ -18,31 +24,26 @@ import org.pyload.android.client.fragments.QueueFragment; import org.pyload.android.client.module.Eula; import org.pyload.android.client.module.GuiTask; +import org.pyload.android.client.services.clicknload.ClickNLoadService; import org.pyload.thrift.Destination; import org.pyload.thrift.PackageDoesNotExists; import org.pyload.thrift.Pyload.Client; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.net.ConnectivityManager; -import android.net.Uri; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.util.Log; -import android.widget.TabHost; -import androidx.core.view.MenuItemCompat; +import java.io.File; +import java.io.FileInputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Locale; public class pyLoad extends FragmentTabsPager { private pyLoadApp app; - // keep reference to set indeterminateProgress private MenuItem refreshItem; /** Called when the activity is first created. */ - public void onCreate(Bundle savedInstanceState) { @@ -90,6 +91,8 @@ public void onCreate(Bundle savedInstanceState) spec = mTabHost.newTabSpec(title).setIndicator(title, res.getDrawable(tab_collector)); mTabsAdapter.addTab(spec, CollectorFragment.class, null); + + startClickNLoadService(); } @Override @@ -359,4 +362,16 @@ public MenuItem getRefreshItem() { return refreshItem; } -} + + private void startClickNLoadService() { + if(app.prefs.getBoolean("check_box_clicknload", false)){ + Intent i= new Intent(getApplicationContext(), ClickNLoadService.class); + i.setAction("START"); + int port = Integer.parseInt(app.prefs.getString("edit_text_clicknload_port", "9666")); + i.putExtra("port", port); + + getApplicationContext().startService(i); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/pyload/android/client/pyLoadApp.java b/app/src/main/java/org/pyload/android/client/pyLoadApp.java index a47d299..00bee60 100644 --- a/app/src/main/java/org/pyload/android/client/pyLoadApp.java +++ b/app/src/main/java/org/pyload/android/client/pyLoadApp.java @@ -14,6 +14,7 @@ import android.annotation.TargetApi; import android.content.Context; +import android.os.Looper; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; @@ -332,4 +333,13 @@ public boolean getCaptchaNotificationShown() return captchaNotificationShown; } + public void showToast(final String text, final int duration){ + new Handler(Looper.getMainLooper()).post(new Runnable(){ + @Override + public void run() { + Toast.makeText(getBaseContext(), text, duration).show(); + } + }); + } + } diff --git a/app/src/main/java/org/pyload/android/client/services/clicknload/ClickNLoadService.java b/app/src/main/java/org/pyload/android/client/services/clicknload/ClickNLoadService.java new file mode 100644 index 0000000..68d296d --- /dev/null +++ b/app/src/main/java/org/pyload/android/client/services/clicknload/ClickNLoadService.java @@ -0,0 +1,51 @@ +package org.pyload.android.client.services.clicknload; + +import android.app.Service; +import android.content.ComponentName; +import android.content.Intent; +import android.os.IBinder; +import android.util.Log; + +import org.pyload.android.client.pyLoadApp; + +import java.util.concurrent.Executors; + +public class ClickNLoadService extends Service { + + private final static String LOGTAG = "ClickNLoad Service"; + + private ClickNLoadTask clickNLoadTask; + private int port; + + @Override + public int onStartCommand(final Intent intent, int flags, int startId) { + + if (intent != null) { + String action = intent.getAction(); + port = intent.getExtras().getInt("port"); + switch(action) { + case "START": startService(); break; + case "STOP": stopService(); break; + default: Log.d(LOGTAG, "This should never happen. No action in the received intent"); + } + } else { + Log.d(LOGTAG, "with a null intent. It has been probably restarted by the system."); + } + + return Service.START_STICKY; + } + + private void stopService() { + clickNLoadTask.stop(); + } + + public void startService() { + clickNLoadTask = new ClickNLoadTask(port, (pyLoadApp) getApplicationContext()); + Executors.newSingleThreadExecutor().submit(clickNLoadTask); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } +} diff --git a/app/src/main/java/org/pyload/android/client/services/clicknload/ClickNLoadTask.java b/app/src/main/java/org/pyload/android/client/services/clicknload/ClickNLoadTask.java new file mode 100644 index 0000000..b3fca86 --- /dev/null +++ b/app/src/main/java/org/pyload/android/client/services/clicknload/ClickNLoadTask.java @@ -0,0 +1,124 @@ +package org.pyload.android.client.services.clicknload; + + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.provider.Settings; +import android.util.Log; +import android.widget.Toast; + +import org.pyload.android.client.R; +import org.pyload.android.client.pyLoadApp; +import org.pyload.thrift.Destination; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URLDecoder; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class ClickNLoadTask implements Runnable{ + + private final static String LOGTAG= "ClickNLoadTask"; + + private volatile boolean stopped = false; + private int port; + private pyLoadApp app; + + public ClickNLoadTask(int port, pyLoadApp app){ + this.port = port; + this.app = app; + + app.prefs.registerOnSharedPreferenceChangeListener(new SharedPreferences.OnSharedPreferenceChangeListener() { + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + //stop if clicknload gets disabled + if(key.equals("check_box_clicknload") && !sharedPreferences.getBoolean(key, true)){ + stop(); + } + } + }); + } + + @Override + public void run() { + Socket clientSocket = null; + BufferedReader in; + PrintStream out; + ServerSocket serverSocket = null; + + while (!stopped) { + + try { + if (serverSocket != null) { + serverSocket.close(); + clientSocket.close(); + } + serverSocket = new ServerSocket(port); + clientSocket = serverSocket.accept(); + + Log.d(LOGTAG, "Receiving ClickNLoad Event"); + in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); + out = new PrintStream(clientSocket.getOutputStream()); + } catch (IOException e) { + Log.e(LOGTAG, "Socket could not be opened", e); + stop(); + break; + } + + try { + String input; + while ((input = in.readLine()) != null) { + if (input.startsWith("source")) { + List<String> urlList = getURLParamsAsMap(input).get("urls"); + app.getClient().addPackage("TestName", urlList, Destination.Collector); + app.showToast(String.format(getLocalizedResources(app).getString(R.string.clicknload_toast_msg), urlList.size()), Toast.LENGTH_LONG); + } + } + out.println("success"); + } catch (Exception e) { + Log.e(LOGTAG, "Data could not be parsed"); + break; + } + } + } + + public static Map<String, List<String>> getURLParamsAsMap(String parameters) throws UnsupportedEncodingException { + final Map<String, List<String>> parameterMap = new LinkedHashMap<>(); + final String[] pairs = parameters.split("&"); + for (String pair : pairs) { + final int idx = pair.indexOf("="); + final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair; + if (!parameterMap.containsKey(key)) { + parameterMap.put(key, new LinkedList<String>()); + } + final String[] values = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8").split("\\r?\\n") : new String[0]; + for (String value : values) { + parameterMap.get(key).add(value); + } + } + return parameterMap; + } + + public void stop() { + stopped = true; + } + + Resources getLocalizedResources(Context context) { + Configuration conf = context.getResources().getConfiguration(); + conf = new Configuration(conf); + conf.setLocale(Locale.getDefault()); + Context localizedContext = context.createConfigurationContext(conf); + return localizedContext.getResources(); + } +} diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 89cc4c9..d9bb24e 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -53,6 +53,11 @@ <string name="captcha_dialog_titel">Bitte geben Sie den Text ein</string> <string name="invert_tabs">Tabfarbe invertieren</string> <string name="invert_tabs_desc">Du kannst das Tab-Aussehen ändern, damit es besser zu deinem System Theme passt.</string> + <string name="enable_clicknload">Aktiviere Click\'n\'Load</string> + <string name="enable_clicknload_desc">Lässt die App auf Click\'n\'Load Verbindungen reagieren.</string> + <string name="clicknload_port">Click\'n\'Load Port</string> + <string name="clicknload_port_desc">Der Server Port für Click\'n\'Load.</string> + <string name="clicknload_toast_msg">Paket mit %d Links empfangen. Zum Collector gesendet</string> <string name="ssl">SSL Verbindung</string> <string name="ssl_desc">Eine sichere Verbindung zum Server aufbauen, wenn SSL aktiviert wurde.</string> <string name="ssl_validate">Bestätige das SSL Zertifikat</string> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 21dc99b..37d033a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -58,6 +58,11 @@ <string name="captcha_notification_desc">Enter captcha to continue downloading.</string> <string name="invert_tabs">Invert tab icon color</string> <string name="invert_tabs_desc">You may change the tab icon layout if it fits better to your theme.</string> + <string name="enable_clicknload">Enable Click\'n\'Load</string> + <string name="enable_clicknload_desc">Have the app listen for Click\'n\'Load connections.</string> + <string name="clicknload_port">Click\'n\'Load Port</string> + <string name="clicknload_port_desc">Server Port for Click\'n\'Load.</string> + <string name="clicknload_toast_msg">Received a package with %d links. Send to Collector</string> <string name="ssl">SSL Connection</string> <string name="ssl_desc">Establish a secure connection to the core if you activated SSL</string> <string name="ssl_validate">Validate SSL Certificate</string> diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 3fbd436..805a1b3 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -12,8 +12,25 @@ <EditTextPreference android:key="refresh_rate" android:defaultValue="5" android:summary="@string/refresh_rate_desc" android:title="@string/refresh_rate" android:numeric="integer"/> <CheckBoxPreference android:key="pull_captcha" android:title="@string/retrieve_captcha" android:summary="@string/retrieve_captcha_desc" android:defaultValue="true"/> <CheckBoxPreference android:key="invert_tabs" android:summary="@string/invert_tabs_desc" android:title="@string/invert_tabs" android:defaultValue="false"/> - - + <CheckBoxPreference + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:defaultValue="false" + android:key="check_box_clicknload" + android:summary="@string/enable_clicknload_desc" + android:title="@string/enable_clicknload" /> + <EditTextPreference + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:defaultValue="9666" + android:digits="0123456789" + android:inputType="number" + android:key="edit_text_clicknload_port" + android:singleLine="true" + android:summary="@string/clicknload_port_desc" + android:title="@string/clicknload_port" /> + + </PreferenceCategory><PreferenceCategory android:key="login" android:title="@string/login"> <EditTextPreference android:key="username" android:summary="@string/username_desc" android:title="@string/username" android:singleLine="true"/> <EditTextPreference android:key="password" android:title="@string/password" android:singleLine="true" android:password="true"/>