diff --git a/README.md b/README.md index a8027b0..4d35246 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +### Development in this repository is stopped. Future development continues on https://github.com/yigit/android-priority-jobqueue +========================== + ![logo](http://downloads.path.com/logo.png) Android Priority Job Queue (Job Manager) @@ -129,7 +132,7 @@ At Path, we use [greenrobot's EventBus](https://github.com/greenrobot/EventBus); ### Getting Started We distribute artifacts through maven central repository. -Gradle: `compile 'com.path:android-priority-jobqueue:1.0'` +Gradle: `compile 'com.path:android-priority-jobqueue:1.1.2'` Maven: @@ -137,7 +140,7 @@ Maven: com.path android-priority-jobqueue - 1.0 + 1.1.2 ``` @@ -150,7 +153,13 @@ We highly recommend checking how you can configure job manager and individual jo * [Review sample configuration][7] ### Version History - + - 1.1.2 (Feb 18, 2014) + - Report exceptions to logger if addInBackground fails. (#31) + - 1.1.1 (Feb 8, 2014) + - Fixed an important bug (#35) where jobs in the same group may run in parallel if many of them become available at the same time while multiple consumer threads are waiting for a new job. + - 1.1 (Jan 30, 2014) + - Job Status query API (#18) + - Fixed a stackoverflow bug when network status changes after a long time. (#21) - 1.0 (Jan 14, 2014): - Added [parameterized][12] constructor for Job for more readable code. - Deprecated `BaseJob` in favor of a more complete `Job` class. diff --git a/examples/twitter/TwitterClient/build.gradle b/examples/twitter/TwitterClient/build.gradle index d5d7bad..cdc0a8d 100644 --- a/examples/twitter/TwitterClient/build.gradle +++ b/examples/twitter/TwitterClient/build.gradle @@ -1,10 +1,15 @@ +task wrapper(type: Wrapper) { + gradleVersion = '1.10' +} + + buildscript { repositories { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:0.7.+' + classpath 'com.android.tools.build:gradle:0.8.+' } } @@ -45,7 +50,7 @@ tasks.create(name: "runTwitter", type: RunApk){ android { compileSdkVersion 17 - buildToolsVersion "18.1.0" + buildToolsVersion "19.0.1" defaultConfig { minSdkVersion 15 targetSdkVersion 17 @@ -53,7 +58,7 @@ android { dependencies { compile 'de.greenrobot:eventbus:2.1.0-beta-1' - compile 'org.twitter4j:twitter4j-core:3.0.3' + compile 'org.twitter4j:twitter4j-core:3.0.5' compile 'com.path:android-priority-jobqueue:1.1-SNAPSHOT' compile files('external-libs/greenDAO.jar') } diff --git a/examples/twitter/TwitterClient/gradlew b/examples/twitter/TwitterClient/gradlew new file mode 100755 index 0000000..91a7e26 --- /dev/null +++ b/examples/twitter/TwitterClient/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/examples/twitter/TwitterClient/gradlew.bat b/examples/twitter/TwitterClient/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/examples/twitter/TwitterClient/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/examples/twitter/TwitterClient/src/com/path/android/jobqueue/examples/twitter/Config.java b/examples/twitter/TwitterClient/src/com/path/android/jobqueue/examples/twitter/Config.java index 0d60b42..7c2e6a7 100644 --- a/examples/twitter/TwitterClient/src/com/path/android/jobqueue/examples/twitter/Config.java +++ b/examples/twitter/TwitterClient/src/com/path/android/jobqueue/examples/twitter/Config.java @@ -1,11 +1,11 @@ package com.path.android.jobqueue.examples.twitter; public class Config { - public static final String CONSUMER_KEY = "W1aPoCdw3QjdKpRm4NdTQ"; - public static final String CONSUMER_SECRET = "fCvy2QV2uSmaaPHRkYQqFA4dZHrusjvQR5lNmEeHI"; + public static final String CONSUMER_KEY = "APEGHy66BMYzvgEktDfc1Q"; + public static final String CONSUMER_SECRET = "wf8XXYwivxHQtiIqPSD3lpq6po9JGRyYBIX0lT0"; - public static final String ACCESS_TOKEN = "1443060589-h6JU83NsHMYx5M47Is2RzlVZmvHPbxQND9xT6KQ"; - public static final String ACCESS_TOKEN_SECRET = "QLut9Mgwge5WptlVnCz9wxmbJrqBFNazkEYrGDZKYE"; + public static final String ACCESS_TOKEN = "1443060589-ZW0lPmcN0NOwy2AchLjZjixHLhPXClYOPQ0IhWG"; + public static final String ACCESS_TOKEN_SECRET = "Tq8aVvT1PA6PXtKHgI5v1EL5UQj3JcGlFzXla2zethjYO"; public static final String REQUEST_TOKEN_URL = "https://api.twitter.com/oauth/request_token"; public static final String AUTHORIZE_URL = "https://api.twitter.com/oauth/authorize"; diff --git a/examples/twitter/TwitterClient/src/com/path/android/jobqueue/examples/twitter/controllers/TwitterController.java b/examples/twitter/TwitterClient/src/com/path/android/jobqueue/examples/twitter/controllers/TwitterController.java index 18e9200..abd4635 100644 --- a/examples/twitter/TwitterClient/src/com/path/android/jobqueue/examples/twitter/controllers/TwitterController.java +++ b/examples/twitter/TwitterClient/src/com/path/android/jobqueue/examples/twitter/controllers/TwitterController.java @@ -3,6 +3,7 @@ import com.path.android.jobqueue.examples.twitter.Config; import twitter4j.*; import twitter4j.auth.AccessToken; +import twitter4j.conf.ConfigurationBuilder; import java.util.List; @@ -21,7 +22,13 @@ public synchronized static TwitterController getInstance() { } public TwitterController() { - twitter = TwitterFactory.getSingleton(); + twitter = new TwitterFactory(new ConfigurationBuilder() + .setOAuthAccessToken(Config.ACCESS_TOKEN) + .setOAuthAccessTokenSecret(Config.ACCESS_TOKEN_SECRET) + .setDebugEnabled(true) + .setOAuthConsumerKey(Config.CONSUMER_KEY) + .setOAuthConsumerKey(Config.CONSUMER_SECRET) + .build()).getSingleton(); AccessToken accessToken = new AccessToken(Config.ACCESS_TOKEN, Config.ACCESS_TOKEN_SECRET); twitter.setOAuthConsumer(Config.CONSUMER_KEY, Config.CONSUMER_SECRET); twitter.setOAuthAccessToken(accessToken); diff --git a/jobqueue/AndroidManifest.xml b/jobqueue/AndroidManifest.xml index 604d7e6..7e37c4b 100644 --- a/jobqueue/AndroidManifest.xml +++ b/jobqueue/AndroidManifest.xml @@ -2,7 +2,7 @@ + android:versionName="1.1.2"> diff --git a/jobqueue/build.gradle b/jobqueue/build.gradle index 856bf65..e6d5fbf 100644 --- a/jobqueue/build.gradle +++ b/jobqueue/build.gradle @@ -1,4 +1,8 @@ import java.util.regex.Pattern +task wrapper(type: Wrapper) { + gradleVersion = '1.10' +} + apply plugin: 'maven' apply plugin: 'signing' @@ -8,7 +12,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:0.7.+' + classpath 'com.android.tools.build:gradle:0.8.+' } } @@ -22,7 +26,7 @@ apply plugin: 'android-library' android { compileSdkVersion 19 - buildToolsVersion "18.1.0" + buildToolsVersion "19.0.1" sourceSets { main { manifest.srcFile 'AndroidManifest.xml' diff --git a/jobqueue/build.xml b/jobqueue/build.xml index a11dd7e..279be48 100644 --- a/jobqueue/build.xml +++ b/jobqueue/build.xml @@ -193,7 +193,7 @@ - + diff --git a/jobqueue/gradle/wrapper/gradle-wrapper.jar b/jobqueue/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..5838598 Binary files /dev/null and b/jobqueue/gradle/wrapper/gradle-wrapper.jar differ diff --git a/jobqueue/gradle/wrapper/gradle-wrapper.properties b/jobqueue/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..59739c3 --- /dev/null +++ b/jobqueue/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Jan 30 14:19:18 PST 2014 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-bin.zip diff --git a/jobqueue/gradlew b/jobqueue/gradlew new file mode 100755 index 0000000..91a7e26 --- /dev/null +++ b/jobqueue/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/jobqueue/gradlew.bat b/jobqueue/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/jobqueue/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/jobqueue/src/com/path/android/jobqueue/JobManager.java b/jobqueue/src/com/path/android/jobqueue/JobManager.java index 4c795a9..1ac6968 100644 --- a/jobqueue/src/com/path/android/jobqueue/JobManager.java +++ b/jobqueue/src/com/path/android/jobqueue/JobManager.java @@ -11,6 +11,7 @@ import com.path.android.jobqueue.nonPersistentQueue.NonPersistentPriorityQueue; import com.path.android.jobqueue.persistentQueue.sqlite.SqliteJobQueue; +import java.util.Collection; import java.util.concurrent.*; /** @@ -40,6 +41,7 @@ public class JobManager implements NetworkEventProvider.Listener { private final ConcurrentHashMap persistentOnAddedLocks; private final ConcurrentHashMap nonPersistentOnAddedLocks; private final ScheduledExecutorService timedExecutor; + private final Object getNextJobLock = new Object(); /** * Default constructor that will create a JobManager with 1 {@link SqliteJobQueue} and 1 {@link NonPersistentPriorityQueue} @@ -260,30 +262,36 @@ private JobHolder getNextJob() { boolean haveNetwork = hasNetwork(); JobHolder jobHolder; boolean persistent = false; - synchronized (nonPersistentJobQueue) { - jobHolder = nonPersistentJobQueue.nextJobAndIncRunCount(haveNetwork, runningJobGroups.getSafe()); - } - if (jobHolder == null) { - //go to disk, there aren't any non-persistent jobs - synchronized (persistentJobQueue) { - jobHolder = persistentJobQueue.nextJobAndIncRunCount(haveNetwork, runningJobGroups.getSafe()); - persistent = true; + synchronized (getNextJobLock) { + final Collection runningJobIds = runningJobGroups.getSafe(); + synchronized (nonPersistentJobQueue) { + jobHolder = nonPersistentJobQueue.nextJobAndIncRunCount(haveNetwork, runningJobIds); } - } - if(jobHolder != null) { - //wait for onAdded locks - if(persistent) { - waitForOnAddedLock(persistentOnAddedLocks, jobHolder.getId()); - } else { - waitForOnAddedLock(nonPersistentOnAddedLocks, jobHolder.getId()); + if (jobHolder == null) { + //go to disk, there aren't any non-persistent jobs + synchronized (persistentJobQueue) { + jobHolder = persistentJobQueue.nextJobAndIncRunCount(haveNetwork, runningJobIds); + persistent = true; + } + } + if(jobHolder == null) { + return null; + } + if(persistent && dependencyInjector != null) { + dependencyInjector.inject(jobHolder.getBaseJob()); + } + if(jobHolder.getGroupId() != null) { + runningJobGroups.add(jobHolder.getGroupId()); } } - if(persistent && jobHolder != null && dependencyInjector != null) { - dependencyInjector.inject(jobHolder.getBaseJob()); - } - if(jobHolder != null && jobHolder.getGroupId() != null) { - runningJobGroups.add(jobHolder.getGroupId()); + + //wait for onAdded locks. wait for locks after job is selected so that we minimize the lock + if(persistent) { + waitForOnAddedLock(persistentOnAddedLocks, jobHolder.getId()); + } else { + waitForOnAddedLock(nonPersistentOnAddedLocks, jobHolder.getId()); } + return jobHolder; } @@ -552,10 +560,14 @@ protected void addJobInBackground(final int priority, final long delay, final Ba timedExecutor.execute(new Runnable() { @Override public void run() { - final long runDelay = (System.nanoTime() - callTime) / NS_PER_MS; - long id = addJob(priority, Math.max(0, delay - runDelay), baseJob); - if(callback != null) { - callback.onAdded(id); + try { + final long runDelay = (System.nanoTime() - callTime) / NS_PER_MS; + long id = addJob(priority, Math.max(0, delay - runDelay), baseJob); + if(callback != null) { + callback.onAdded(id); + } + } catch (Throwable t) { + JqLog.e(t, "addJobInBackground received an exception. job class: %s", baseJob.getClass().getSimpleName() ); } } }); diff --git a/jobqueue/src/com/path/android/jobqueue/Params.java b/jobqueue/src/com/path/android/jobqueue/Params.java index c3e323a..1695d94 100644 --- a/jobqueue/src/com/path/android/jobqueue/Params.java +++ b/jobqueue/src/com/path/android/jobqueue/Params.java @@ -97,23 +97,23 @@ public Params setDelayMs(long delayMs) { return this; } - boolean doesRequireNetwork() { + public boolean doesRequireNetwork() { return requiresNetwork; } - String getGroupId() { + public String getGroupId() { return groupId; } - boolean isPersistent() { + public boolean isPersistent() { return persistent; } - int getPriority() { + public int getPriority() { return priority; } - long getDelayMs() { + public long getDelayMs() { return delayMs; } } diff --git a/jobqueue/test/com/path/android/jobqueue/test/jobmanager/GroupingTest.java b/jobqueue/test/com/path/android/jobqueue/test/jobmanager/GroupingTest.java index 00103e9..0739d3c 100644 --- a/jobqueue/test/com/path/android/jobqueue/test/jobmanager/GroupingTest.java +++ b/jobqueue/test/com/path/android/jobqueue/test/jobmanager/GroupingTest.java @@ -3,6 +3,7 @@ import com.path.android.jobqueue.JobHolder; import com.path.android.jobqueue.JobManager; import com.path.android.jobqueue.Params; +import com.path.android.jobqueue.config.Configuration; import com.path.android.jobqueue.test.jobs.DummyJob; import org.fest.reflect.method.*; import static org.hamcrest.CoreMatchers.*; @@ -11,6 +12,9 @@ import org.junit.runner.RunWith; import org.robolectric.*; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + @RunWith(RobolectricTestRunner.class) public class GroupingTest extends JobManagerTestBase { @Test @@ -35,4 +39,68 @@ public void testGrouping() throws Exception { removeJobMethod.invoke(group2Job); MatcherAssert.assertThat("even after group2 job is complete, no jobs should be returned since we only have group1 jobs left", nextJobMethod.invoke(), is(nullValue())); } + + @Test + public void testGroupingRaceCondition() throws Exception { + DummyNetworkUtilWithConnectivityEventSupport dummyNetworkUtil = new DummyNetworkUtilWithConnectivityEventSupport(); + JobManager jobManager = createJobManager(new Configuration.Builder(Robolectric.application) + .minConsumerCount(5).maxConsumerCount(10) + .networkUtil(dummyNetworkUtil)); + dummyNetworkUtil.setHasNetwork(false, true); + //add a bunch of network requring jobs + final String GROUP_ID = "shared_group_id"; + final int AFTER_ADDED_JOBS_COUNT = 5; + final int NOT_SET_JOB_ID = -1; + final AtomicInteger firstRunJob = new AtomicInteger(NOT_SET_JOB_ID); + final int FIRST_JOB_ID = -10; + final CountDownLatch onAddedCalled = new CountDownLatch(1); + final CountDownLatch remainingJobsOnAddedCalled = new CountDownLatch(AFTER_ADDED_JOBS_COUNT); + jobManager.addJobInBackground(new DummyJob(new Params(10).requireNetwork().groupBy(GROUP_ID)) { + @Override + public void onAdded() { + super.onAdded(); + onAddedCalled.countDown(); + try { + //wait until all other jobs are added + remainingJobsOnAddedCalled.await(); + //wait a bit after all are added, + Thread.sleep(1000); + } catch (InterruptedException e) { + } + } + + @Override + public void onRun() throws Throwable { + super.onRun(); + firstRunJob.compareAndSet(NOT_SET_JOB_ID, FIRST_JOB_ID); + } + }); + //ensure first jobs on added is called + onAddedCalled.await(); + for(int i = 0; i < AFTER_ADDED_JOBS_COUNT; i ++) { + final int finalI = i; + jobManager.addJob(new DummyJob(new Params(5).groupBy(GROUP_ID).requireNetwork()) { + final int id = finalI + 1; + + @Override + public void onAdded() { + super.onAdded(); + remainingJobsOnAddedCalled.countDown(); + } + + @Override + public void onRun() throws Throwable { + super.onRun(); + firstRunJob.compareAndSet(NOT_SET_JOB_ID, id); + } + }); + } + dummyNetworkUtil.setHasNetwork(true, true); + //wait until all jobs are completed + while(firstRunJob.get() == NOT_SET_JOB_ID) { + Thread.sleep(100); + } + MatcherAssert.assertThat("highest priority job should run if it is added before others", firstRunJob.get(), is(FIRST_JOB_ID)); + + } } diff --git a/jobqueue/test/com/path/android/jobqueue/test/jobmanager/InjectorTest.java b/jobqueue/test/com/path/android/jobqueue/test/jobmanager/InjectorTest.java index ac6152f..7129710 100644 --- a/jobqueue/test/com/path/android/jobqueue/test/jobmanager/InjectorTest.java +++ b/jobqueue/test/com/path/android/jobqueue/test/jobmanager/InjectorTest.java @@ -6,6 +6,7 @@ import com.path.android.jobqueue.Params; import com.path.android.jobqueue.config.Configuration; import com.path.android.jobqueue.di.DependencyInjector; +import com.path.android.jobqueue.log.CustomLogger; import com.path.android.jobqueue.test.jobs.DummyJob; import static org.hamcrest.CoreMatchers.*; import org.hamcrest.*; @@ -13,6 +14,8 @@ import org.junit.runner.RunWith; import org.robolectric.*; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @RunWith(RobolectricTestRunner.class) @@ -42,6 +45,55 @@ public void inject(BaseJob job) { holder = getNextJobMethod(jobManager).invoke(); MatcherAssert.assertThat("injection should be called for persistent job", holder.getBaseJob(), equalTo(injectedJobReference.getObject())); MatcherAssert.assertThat("injection should be called two times for persistent job", injectionCallCount.get(), equalTo(3)); + } + + @Test + public void testInjectorCrash() throws Exception { + final String EXCEPTION_MESSAGE = "could not inject for whatever reason :)"; + DependencyInjector dummyDependencyInjector = new DependencyInjector() { + @Override + public void inject(BaseJob baseJob) { + throw new RuntimeException(EXCEPTION_MESSAGE); + } + }; + + final ObjectReference objectReference = new ObjectReference(); + final CountDownLatch exceptionLatch = new CountDownLatch(1); + CustomLogger customLogger = new CustomLogger() { + @Override + public boolean isDebugEnabled() { + return false; + } + + @Override + public void d(String s, Object... objects) { + + } + + @Override + public void e(Throwable throwable, String s, Object... objects) { + objectReference.setObject(throwable); + exceptionLatch.countDown(); + } + + @Override + public void e(String s, Object... objects) { + // + } + }; + JobManager jobManager = createJobManager(new Configuration.Builder(Robolectric.application).injector(dummyDependencyInjector).customLogger(customLogger)); + Throwable addException = null; + try { + jobManager.addJob(new DummyJob(new Params(0))); + } catch (Throwable t) { + addException = t; + } + MatcherAssert.assertThat("addJob should throw exception if dependency injector throws exception", addException, notNullValue()); + jobManager.addJobInBackground(new DummyJob(new Params(0))); + exceptionLatch.await(2, TimeUnit.SECONDS); + MatcherAssert.assertThat("there should be a received exception", objectReference.getObject(), notNullValue()); + MatcherAssert.assertThat("logged exception should be a runtime exception", objectReference.getObject(), instanceOf(RuntimeException.class)); + MatcherAssert.assertThat("logged exception should have expected message", ((Throwable)objectReference.getObject()).getMessage(), is(EXCEPTION_MESSAGE)); } }