├── .gitattributes ├── .gitignore ├── COPYING ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── de │ └── haukerehfeld │ └── quakeinjector │ ├── BuildCommit.java │ ├── Cancelable.java │ ├── ChangeListenerList.java │ ├── CheckCanceledProgressListener.java │ ├── CheckInstalled.java │ ├── CompressedProgressListener.java │ ├── Configuration.java │ ├── Download.java │ ├── DownloadWorker.java │ ├── DumpInputStream.java │ ├── EngineConfigDialog.java │ ├── EngineOutputDialog.java │ ├── EngineStarter.java │ ├── FileInfo.java │ ├── FileNotWritableException.java │ ├── HTTPException.java │ ├── InspectZipWorker.java │ ├── InstallQueuePanel.java │ ├── InstallWorker.java │ ├── InstalledPackageList.java │ ├── InstalledPackages.java │ ├── Installer.java │ ├── Menu.java │ ├── OnlineFileNotFoundException.java │ ├── Package.java │ ├── PackageDatabaseParser.java │ ├── PackageDatabaseParserWorker.java │ ├── PackageDetailPanel.java │ ├── PackageFileList.java │ ├── PackageInteractionPanel.java │ ├── PackageList.java │ ├── PackageListSelectionHandler.java │ ├── PackageOverwriteDialog.java │ ├── PackageTable.java │ ├── PercentageProgressListener.java │ ├── ProgressListener.java │ ├── ProgressListenerInputStream.java │ ├── QuakeInjector.java │ ├── RelativePath.java │ ├── Requirement.java │ ├── RequirementList.java │ ├── RuntimeExecutionException.java │ ├── SaveInstalled.java │ ├── SortableRequirement.java │ ├── SumProgressListener.java │ ├── ThreadedGetter.java │ ├── UnavailableRequirement.java │ ├── UninstallWorker.java │ ├── UpdateRequirementListWorker.java │ ├── Utils.java │ ├── XmlUtils.java │ ├── ZipInspect.java │ ├── database │ └── InstalledMapsParser.java │ ├── gui │ ├── BrowserLauncher.java │ ├── ClosePanel.java │ ├── ErrorEvent.java │ ├── ErrorListener.java │ ├── JPathPanel.java │ ├── LabelFieldPanel.java │ ├── LookAndFeelDefaults.java │ ├── OkayCancelApplyPanel.java │ ├── ProgressListener.java │ ├── ProgressPopup.java │ ├── ScrollablePanel.java │ └── SimpleErrorEvent.java │ └── packagelist │ └── model │ ├── Column.java │ └── PackageListModel.java └── resources ├── Inject2.ico ├── Inject2_16.png ├── Inject2_16_16colors.png ├── Inject2_16_256colors..png ├── Inject2_256.png ├── Inject2_32.png ├── Inject2_32_16colors.png ├── Inject2_32_256colors..png ├── Inject2_48.png ├── Inject2_48_16colors.png ├── Inject2_48_256colors..png ├── star_spirit_12.png ├── star_spirit_8.png └── star_spirit_8_inactive.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # These are explicitly windows files and should use crlf 5 | *.bat text eol=crlf 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | dist 3 | release 4 | build-info.properties 5 | 6 | # Ignore Gradle project-specific cache directory 7 | .gradle 8 | # Ignore Gradle build output directory 9 | build 10 | 11 | 12 | database.xml 13 | installedMaps.xml 14 | config.properties 15 | jre/ 16 | 17 | # ide files 18 | # These can be regenerated with `gradlew eclipse` so they no longer need to be committed 19 | .classpath 20 | .project 21 | .settings/ 22 | 23 | *.iml 24 | .idea/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quake Injector 2 | Quake Injector is a tool to *download, install and play* Quake (some call it "Quake 1") singleplayer maps from the "Quaddicted archive":https://www.quaddicted.com - basically the complete history of all quake singleplayer maps ever made. Ever been annoyed by an excruciating installation process? Could not figure out how to install a certain map? Well, this tool makes it a piece of cake. Simply pick a map and click Install. 3 | 4 | ![Screenshot of the Quake Injector](https://www.quaddicted.com/_media/quakeinjector_20091117.png "Screenshot of the Quake Injector") 5 | 6 | ## Features 7 | - Simply pick a map, click install and play 8 | - [All Quake singleplayer maps ever made](https://www.quaddicted.com/reviews/) 9 | - Filter, sort, browse to find your favourite map 10 | - Automatically installs dependencies (e.g. Quoth) 11 | - Cross-Platform 12 | - [Free, Open Source Software](https://github.com/hrehfeld/QuakeInjector/), released under the GPL. 13 | - Scan your Quake directory for already installed maps 14 | 15 | ## Installation 16 | On Windows you can use a self-contained executable. On Linux and MacOS you need Java. 17 | 18 | ### Windows 19 | 1. Download the latest ["QuakeInjector.exe" release](https://github.com/hrehfeld/QuakeInjector/releases) 20 | 1. Extract the .zip file 21 | 1. Double click the QuakeInjector.exe 22 | 23 | ### Linux and MacOS 24 | 1. Make sure you have a recent Java installed 25 | 1. Download the latest [Quake Injector release](https://github.com/hrehfeld/QuakeInjector/releases) 26 | 1. Extract the .zip file 27 | 1. Double click `lib/quakeinjector.jar`, or start it with `java -jar lib/quakeinjector.jar`. On Windows, you can also use the supplied `quakeinjector.bat`. 28 | 29 | *Keep in mind this software is still in heavy development. The alpha tag hints at it not being feature complete, and possibly buggy.* See the included licence (`COPYING`) for more details on warranties, etc. (there are none). 30 | 31 | ## Basic Manual 32 | ### Filtering, Sorting and the Package Table 33 | Right above the table of available packages you see a filter field. Here you can specify a filter for the listing. Let's say you want to see only maps from 2001? Enter "`2001`" without the quotes. Or for all maps by Vondur, enter "`Vondur`". You can also enter multiple words, they will be connect by a logical AND. So "`czg 2000`" would show you all maps czg made in 2000. To reset the filter simply delete all text from it. 34 | 35 | At the top of the package list there are the table headers (duh!). Click on them to sort the table. 36 | 37 | This works well in combination with a filter. For example you could look at all maps from 2008 sorted by rating. 38 | 39 | ## Known Problems 40 | 1. On **Linux/MacOS** and other case-sensitive operating systems you won’t have much joy as there is no engine that can ignore the case of filenames *yet*. There will be one and it will be a good one, but for now you're pretty much on your own, as **a lot of maps were packed with mixed case filenames** by the original authors. 41 | 42 | 1. Also be aware that most engines cannot run some of the latest and greatest maps, because those **maps break the original quake engine limits**. A [modern Quake engine](https://neogeographica.com/site/pages/guides/engines.html#04) is recommended. 43 | 44 | ## Development 45 | ### Bugs and feedback 46 | If anything that feels weird occurs to you, or you find a definite bug, please [report it as an issue](https://github.com/hrehfeld/QuakeInjector/issues). Likewise, give us a shout if you want to help with development. Pull requests are what powers the project! 47 | 48 | ### Running from source 49 | To run the application for development, run the following command in the root directory. 50 | 51 | On Windows: 52 | ``` 53 | gradlew.bat run 54 | ``` 55 | On Unix: 56 | ``` 57 | ./gradlew run 58 | ``` 59 | 60 | ### Building 61 | To build a jar, grab the source and run the following command in the root directory. The jar will be in the `build/libs` directory. 62 | 63 | On Windows: 64 | ``` 65 | gradlew.bat assemble 66 | ``` 67 | On Unix: 68 | ```bash 69 | ./gradlew assemble 70 | ``` 71 | 72 | ### Building a Windows EXE 73 | To build an EXE for Windows, grab the source and run the following command in the root directory. It will create a zip in the `build/distributions` directory. 74 | 75 | On Windows: 76 | ``` 77 | gradlew.bat winDist 78 | ``` 79 | On Unix: 80 | ```bash 81 | ./gradlew winDist 82 | ``` 83 | 84 | ### Notes 85 | - Use an annotated Git tag to define the name of the resulting JAR: `git tag -a "alphaXX" -m "alphaXX"` will make gradlew compile `QuakeInjector-alphaXX.jar`. 86 | 87 | ## Credits 88 | - Hauke 'megaman' Rehfeld (initial programming) 89 | - Spirit (initial concept) 90 | - [These lovely people](https://github.com/hrehfeld/QuakeInjector/graphs/contributors) (updates, fixes, features, maintenance) ❤️ 91 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'eclipse' 4 | id 'application' 5 | // id 'com.palantir.graal' version '0.9.0' 6 | id "com.github.spotbugs" version "4.7.1" 7 | id 'edu.sc.seis.launch4j' version '2.5.0' 8 | } 9 | 10 | version = "git describe --tags".execute().text.trim() 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | dependencies { 16 | implementation 'edu.stanford.ejalbert:BrowserLauncher2:1.3' 17 | } 18 | 19 | application { 20 | mainClass.set('de.haukerehfeld.quakeinjector.QuakeInjector') 21 | } 22 | 23 | jar { 24 | manifest { 25 | attributes["Main-Class"] = application.mainClass.get() 26 | } 27 | } 28 | 29 | //graal { 30 | // javaVersion "11" 31 | // graalVersion "21.1.0" 32 | // outputName "quake-injector" 33 | // mainClass application.mainClassName 34 | // option "--no-fallback" 35 | // option "--verbose" 36 | //} 37 | 38 | task readCommit(type: WriteProperties) { 39 | outputFile = file("${buildDir}/build-info.properties") 40 | comment = "Revision name" 41 | property("quake-injector.build-commit", version) 42 | } 43 | 44 | launch4j { 45 | mainClassName = application.mainClass.get() 46 | icon = "${projectDir}/src/main/resources/Inject2.ico" 47 | bundledJrePath "./runtime" 48 | bundledJre64Bit false 49 | } 50 | 51 | processResources { 52 | from(readCommit) 53 | } 54 | 55 | task downloadJre { 56 | def targetDir = "$buildDir/jre" 57 | def f = new File("$buildDir/jre.zip") 58 | f.parentFile.mkdir() 59 | if (!f.exists()) { 60 | new URL('https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.10%2B7/OpenJDK17U-jdk_x86-32_windows_hotspot_17.0.10_7.zip').withInputStream {i -> f.withOutputStream {it << i}} 61 | } 62 | 63 | copy { 64 | from zipTree("$buildDir/jre.zip") 65 | into(targetDir) 66 | fileMode 0777 67 | dirMode 0777 68 | } 69 | } 70 | 71 | task setupWinDist(type: Copy) { 72 | dependsOn("createExe", "downloadJre") 73 | group "distribution" 74 | def targetDir = "$buildDir/winDist" 75 | mkdir(targetDir) 76 | File jreDir 77 | doLast { 78 | copy { 79 | file("$buildDir/jre").eachDir { 80 | if (it.name.contains("jdk")) { 81 | jreDir = it 82 | } 83 | } 84 | if (jreDir) { 85 | from(jreDir) 86 | into("$targetDir/runtime") 87 | } 88 | } 89 | } 90 | from("$buildDir/launch4j") 91 | into(targetDir) 92 | } 93 | 94 | task winDist(type: Zip) { 95 | dependsOn "setupWinDist" 96 | group "distribution" 97 | def distDir = "$buildDir/winDist" 98 | from "$distDir" 99 | archiveFileName = "QuakeInjector.exe-${project.version}.zip" 100 | exclude("*-jre") 101 | 102 | } 103 | test { 104 | useJUnitPlatform() 105 | } 106 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrehfeld/QuakeInjector/65fe5924c7703038e7e2eedc5ae2e17cda19560a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Apr 12 17:48:37 EDT 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | 88 | @rem Execute Gradle 89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 90 | 91 | :end 92 | @rem End local scope for the variables with windows NT shell 93 | if "%ERRORLEVEL%"=="0" goto mainEnd 94 | 95 | :fail 96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 97 | rem the _cmd.exe /c_ return code! 98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 99 | exit /b 1 100 | 101 | :mainEnd 102 | if "%OS%"=="Windows_NT" endlocal 103 | 104 | :omega 105 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'QuakeInjector' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/BuildCommit.java: -------------------------------------------------------------------------------- 1 | package de.haukerehfeld.quakeinjector; 2 | 3 | import java.io.IOException; 4 | import java.util.Properties; 5 | 6 | public final class BuildCommit { 7 | private static String buildCommit = ""; 8 | 9 | public static String getBuildCommit(){ 10 | if (buildCommit == null || buildCommit.isEmpty()){ 11 | Properties properties = new Properties(); 12 | try { 13 | System.out.println("No build commit found, attempting to read from props"); 14 | properties.load(BuildCommit.class.getClassLoader().getResourceAsStream( 15 | "build-info.properties")); 16 | 17 | } catch (IOException e) { 18 | e.printStackTrace(); 19 | } 20 | 21 | buildCommit = properties.getProperty("quake-injector.build-commit"); 22 | System.out.println("Build commit is " + buildCommit); 23 | } 24 | return buildCommit; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/Cancelable.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | 21 | package de.haukerehfeld.quakeinjector; 22 | 23 | public interface Cancelable { 24 | public void checkCancelled() throws Installer.CancelledException; 25 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/ChangeListenerList.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | import java.util.ArrayList; 23 | 24 | import javax.swing.event.ChangeEvent; 25 | import javax.swing.event.ChangeListener; 26 | 27 | 28 | /** 29 | * Helper class for easy changelistener lists 30 | */ 31 | public class ChangeListenerList { 32 | private ArrayList listeners = new ArrayList(); 33 | 34 | public void addChangeListener(ChangeListener l) { 35 | 36 | if (listeners.indexOf(l) >= 0) { 37 | return; 38 | } 39 | listeners.add(l); 40 | } 41 | 42 | public void removeChangeListener(ChangeListener l) { 43 | listeners.remove(l); 44 | } 45 | 46 | public void notifyChangeListeners(Object source) { 47 | ChangeEvent e = new ChangeEvent(source); 48 | for (ChangeListener l: listeners) { 49 | l.stateChanged(e); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/CheckCanceledProgressListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | 21 | package de.haukerehfeld.quakeinjector; 22 | 23 | /** 24 | * Check if canceledd on each write 25 | */ 26 | public class CheckCanceledProgressListener implements ProgressListener { 27 | private final ProgressListener progress; 28 | private final Cancelable toCheck; 29 | 30 | public CheckCanceledProgressListener(Cancelable toCheck, ProgressListener progress) { 31 | this.progress = progress; 32 | this.toCheck = toCheck; 33 | } 34 | 35 | /** 36 | * tell the reporter of the size of uncompression 37 | */ 38 | public void publish(long writtenBytes) { 39 | toCheck.checkCancelled(); 40 | progress.publish(writtenBytes); 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/CheckInstalled.java: -------------------------------------------------------------------------------- 1 | package de.haukerehfeld.quakeinjector; 2 | 3 | import javax.swing.SwingWorker; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.Collections; 8 | 9 | import java.io.File; 10 | import java.io.InputStream; 11 | import java.io.FileInputStream; 12 | import java.io.BufferedInputStream; 13 | import javax.swing.JOptionPane; 14 | 15 | 16 | class CheckInstalled extends SwingWorker, Void> 17 | implements ProgressListener { 18 | 19 | String zipContentsDatabaseUrl; 20 | String enginePath; 21 | RequirementList maps; 22 | SaveInstalled saveInstalled; 23 | QuakeInjector injector; 24 | 25 | public CheckInstalled(QuakeInjector injector, 26 | String zipContentsDatabaseUrl, 27 | String enginePath, 28 | RequirementList maps, 29 | SaveInstalled saveInstalled) { 30 | this.zipContentsDatabaseUrl = zipContentsDatabaseUrl; 31 | this.enginePath = enginePath; 32 | this.maps = maps; 33 | this.injector = injector; 34 | this.saveInstalled = saveInstalled; 35 | } 36 | 37 | @Override 38 | public List doInBackground() throws java.lang.InterruptedException, 39 | java.util.concurrent.ExecutionException, 40 | java.io.IOException { 41 | 42 | List packages = Collections.emptyList(); 43 | { 44 | //get download stream 45 | Download d = Download.create(zipContentsDatabaseUrl); 46 | d.connect(); 47 | final InputStream dl = d.getStream(); 48 | 49 | try { 50 | packages = new InstalledPackageList().read(dl); 51 | 52 | Collections.sort(packages); 53 | } 54 | catch (java.io.FileNotFoundException e) { 55 | System.out.println("Notice: installed maps file doesn't exist yet," 56 | + " no maps installed? " + e); 57 | } 58 | catch (java.io.IOException e) { 59 | System.err.println("Error: installed maps file couldn't be loaded: " + e); 60 | e.printStackTrace(); 61 | } 62 | } 63 | 64 | int i = 0; 65 | List installed = new ArrayList(); 66 | for (PackageFileList list: packages) { 67 | publish(i++ * 100 / packages.size()); 68 | Requirement r = maps.get(list.getId()); 69 | String basedir = enginePath + File.separator; 70 | if (r instanceof UnavailableRequirement) { 71 | continue; 72 | } 73 | 74 | List missingFiles = new ArrayList(); 75 | for (FileInfo entry: list) { 76 | if (isCancelled()) { 77 | throw new java.util.concurrent.CancellationException(); 78 | } 79 | 80 | if (list.size() > 7 && missingFiles.size() > 0.2f * list.size()) { 81 | System.out.println("Too many missing files for " + list.getId() 82 | + ", stopping search!"); 83 | break; 84 | } 85 | 86 | String filename = entry.getName(); 87 | //System.out.println("Basedir: " + basedir + "; filename: " + filename); 88 | String file = basedir + filename; 89 | long supposedCrc = entry.getChecksum(); 90 | File f = new File(file); 91 | System.out.print("Checking for " + f + "..."); 92 | if (!f.exists()) { 93 | if (entry.getEssential()) { 94 | missingFiles.add(file); 95 | System.out.println("missing!"); 96 | } 97 | } 98 | else { 99 | System.out.println("found!"); 100 | if (!f.isDirectory()) { 101 | long crc = Utils.getCrc32(new BufferedInputStream(new FileInputStream(f)), null); 102 | if (supposedCrc != 0 && crc != entry.getChecksum()) { 103 | System.err.println("Crc differs for file " + file); 104 | if (entry.getEssential()) { 105 | System.out.println("Counting as missing."); 106 | missingFiles.add(file); 107 | } 108 | } 109 | // else { 110 | // System.out.println("Crc matches for " + f + " (" + crc + ")"); 111 | // } 112 | } 113 | } 114 | } 115 | 116 | if (missingFiles.isEmpty()) { 117 | System.out.println(list.getId() + " seems to be installed."); 118 | //if we would allow for missing files in an installed package, we'd need to have custom file lists! 119 | installed.add(list); 120 | } 121 | else { 122 | System.out.println(list.getId() + " has missing files, not installed."); 123 | } 124 | } 125 | 126 | return installed; 127 | } 128 | 129 | 130 | @Override 131 | public void done() { 132 | try { 133 | List list = get(); 134 | 135 | injector.setInstalledStatus(list); 136 | 137 | synchronized (maps) { 138 | saveInstalled.write(maps); 139 | } 140 | } 141 | catch (java.lang.InterruptedException e) { 142 | System.err.println("Interrupted: " + e); 143 | e.printStackTrace(); 144 | } 145 | catch (java.util.concurrent.ExecutionException e) { 146 | System.err.println("Exception: " + e); 147 | e.printStackTrace(); 148 | try { 149 | throw e.getCause(); 150 | } 151 | catch (java.net.ConnectException err) { 152 | String msg = "Downloading file database failed, " + err.getMessage() + "!"; 153 | JOptionPane.showMessageDialog(injector, 154 | msg, 155 | "Downloading failed!", 156 | JOptionPane.ERROR_MESSAGE); 157 | 158 | } 159 | catch (Throwable err) { 160 | } 161 | } 162 | catch (java.util.concurrent.CancellationException e) { 163 | } 164 | catch (java.io.IOException e) { 165 | System.err.println("Couldn't write installedMapsFile: " + e); 166 | e.printStackTrace(); 167 | } 168 | } 169 | 170 | public void publish(long progress) { 171 | if (progress <= 100) { 172 | setProgress((int) progress); 173 | } 174 | } 175 | 176 | } 177 | 178 | 179 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/CompressedProgressListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | 21 | package de.haukerehfeld.quakeinjector; 22 | 23 | /** 24 | * Report progress from the uncompressed values where only the compressed size is known 25 | */ 26 | public class CompressedProgressListener implements ProgressListener { 27 | private final double compressionRatio; 28 | private final ProgressListener progress; 29 | 30 | public CompressedProgressListener(double compressionRatio, 31 | ProgressListener progress) { 32 | this.compressionRatio = compressionRatio; 33 | this.progress = progress; 34 | } 35 | 36 | /** 37 | * tell the reporter of the size of uncompression 38 | */ 39 | public void publish(long writtenBytes) { 40 | if (writtenBytes <= 0) { 41 | System.err.println("Progress <= 0 written bytes: " + writtenBytes); 42 | } 43 | long downloaded = (long) (writtenBytes * compressionRatio); 44 | progress.publish(downloaded); 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/Download.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | 21 | package de.haukerehfeld.quakeinjector; 22 | 23 | import java.io.FileNotFoundException; 24 | import java.io.IOException; 25 | import java.io.InputStream; 26 | import java.net.HttpURLConnection; 27 | import java.net.MalformedURLException; 28 | import java.net.URI; 29 | import java.net.URISyntaxException; 30 | import java.net.URL; 31 | import java.net.URLConnection; 32 | import java.util.zip.GZIPInputStream; 33 | import java.util.zip.Inflater; 34 | import java.util.zip.InflaterInputStream; 35 | 36 | public class Download { 37 | private final URL url; 38 | private InputStream stream; 39 | private URLConnection connection; 40 | 41 | public static Download create(String urlString) throws IOException { 42 | URL cleanUrl; 43 | try { 44 | // URLs with spaces in the path need escaping to %20, not +. We can't use built in URLEncoder 45 | URL url= new URL(urlString); 46 | URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef()); 47 | 48 | cleanUrl = new URL(uri.toASCIIString()); 49 | } 50 | catch (MalformedURLException | URISyntaxException e) { 51 | throw new RuntimeException("Something is wrong with the way we construct URLs: " 52 | + e.getMessage()); 53 | } 54 | return new Download(cleanUrl); 55 | } 56 | 57 | public Download(URL url) throws IOException { 58 | this.url = url; 59 | } 60 | 61 | public void connect() throws IOException { 62 | try { 63 | connection = url.openConnection(); 64 | 65 | //http stuff, but url might be a different protocol 66 | HttpURLConnection con = null; 67 | if (connection instanceof HttpURLConnection) { 68 | con = (HttpURLConnection) connection; 69 | HttpURLConnection.setFollowRedirects(true); 70 | con.setRequestProperty("Accept-Encoding","gzip, deflate"); 71 | con.setRequestProperty("User-Agent","Quakeinjector-" + BuildCommit.getBuildCommit()); 72 | } 73 | 74 | connection.connect(); 75 | 76 | if (con != null) { 77 | int response = con.getResponseCode(); 78 | if (response != HttpURLConnection.HTTP_OK) { 79 | throw new HTTPException(response); 80 | } 81 | } 82 | 83 | //try getting the stream 84 | connection.getInputStream(); 85 | } 86 | catch (FileNotFoundException e) { 87 | throw new OnlineFileNotFoundException(e.getMessage()); 88 | } 89 | } 90 | 91 | 92 | public InputStream getStream() throws IOException { 93 | return getStream(null); 94 | } 95 | 96 | public InputStream getStream(ProgressListener progress) throws IOException { 97 | if (stream == null) { 98 | String encoding = connection.getContentEncoding(); 99 | 100 | 101 | stream = connection.getInputStream(); 102 | if (progress != null) { 103 | stream = new ProgressListenerInputStream(stream, progress); 104 | } 105 | 106 | /** @todo 2009-07-15 18:55 hrehfeld check what this does for non-http connections */ 107 | if (encoding != null && encoding.equalsIgnoreCase("gzip")) { 108 | stream = new GZIPInputStream(stream); 109 | } 110 | else if (encoding != null && encoding.equalsIgnoreCase("deflate")) { 111 | stream = new InflaterInputStream(stream, new Inflater(true)); 112 | } 113 | } 114 | 115 | return stream; 116 | } 117 | 118 | public int getSize() { 119 | return connection.getContentLength(); 120 | } 121 | 122 | public String toString() { 123 | return "<" + url.toString() + ">"; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/DownloadWorker.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | import java.io.FileNotFoundException; 23 | import java.io.IOException; 24 | import java.io.InputStream; 25 | import java.io.OutputStream; 26 | import javax.swing.SwingWorker; 27 | 28 | /** 29 | * Install maps in a worker thread 30 | * Init once and let swing start it - don't reuse 31 | */ 32 | public class DownloadWorker extends SwingWorker implements ProgressListener, Cancelable { 33 | private final static int BUFFERSIZE = 1024; 34 | 35 | private final Download download; 36 | private final OutputStream out; 37 | 38 | public DownloadWorker(final Download download, 39 | final OutputStream out) { 40 | this.download = download; 41 | this.out = out; 42 | } 43 | 44 | @Override 45 | public Long doInBackground() throws 46 | IOException, 47 | FileNotFoundException, 48 | Installer.CancelledException { 49 | 50 | System.out.println("Downloading " + download); 51 | 52 | 53 | download.connect(); 54 | 55 | //build progress filter chain 56 | final ProgressListener progress = 57 | new SumProgressListener( 58 | new PercentageProgressListener(download.getSize(), 59 | new CheckCanceledProgressListener(this, 60 | this))); 61 | 62 | 63 | final InputStream in = download.getStream(); 64 | final byte data[] = new byte[BUFFERSIZE]; 65 | long downloadSize = 0; 66 | int readcount; 67 | while ((readcount = in.read(data, 0, BUFFERSIZE)) != -1) { 68 | progress.publish(readcount); 69 | out.write(data, 0, readcount); 70 | downloadSize += readcount; 71 | } 72 | out.flush(); 73 | return downloadSize; 74 | } 75 | 76 | public void publish(long progress) { 77 | if (progress <= 100) { 78 | setProgress((int) progress); 79 | } 80 | } 81 | 82 | public void checkCancelled() throws Installer.CancelledException { 83 | if (isCancelled()) { 84 | System.out.println("canceling..."); 85 | throw new Installer.CancelledException(); 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/DumpInputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | 21 | package de.haukerehfeld.quakeinjector; 22 | 23 | import java.io.IOException; 24 | import java.io.InputStream; 25 | import java.io.OutputStream; 26 | 27 | /** 28 | * Dump everything that's read to an OutputStream 29 | 30 | * Note that skipping doesn't add to the stream, and marking isn't supported 31 | */ 32 | public class DumpInputStream extends InputStream { 33 | private final InputStream in; 34 | private final OutputStream out; 35 | 36 | public DumpInputStream(InputStream in, OutputStream out) { 37 | this.in = in; 38 | this.out = out; 39 | } 40 | 41 | @Override 42 | public int read() throws IOException { 43 | int r = in.read(); 44 | out.write(r); 45 | return r; 46 | } 47 | 48 | @Override 49 | public int read(byte[] b) throws IOException { 50 | return read(b, 0, b.length); 51 | } 52 | 53 | @Override 54 | public int read(byte[] b, int off, int len) throws IOException { 55 | int readcount = in.read(b, off, len); 56 | if (readcount > 0) { 57 | out.write(b, off, readcount); 58 | } 59 | return readcount; 60 | } 61 | 62 | @Override 63 | public long skip(long n) throws IOException { 64 | return in.skip(n); 65 | } 66 | 67 | @Override 68 | public int available() throws IOException { 69 | return in.available(); 70 | } 71 | 72 | @Override 73 | public void mark(int readlimit) {} 74 | 75 | @Override 76 | public void reset() throws IOException { 77 | throw new IOException("Reset not supported"); 78 | } 79 | 80 | @Override 81 | public boolean markSupported() { 82 | return false; 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/EngineOutputDialog.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | import java.awt.BorderLayout; 23 | import java.awt.Rectangle; 24 | import java.awt.event.ActionEvent; 25 | import java.awt.event.ActionListener; 26 | import java.awt.event.KeyEvent; 27 | import java.io.BufferedReader; 28 | import java.io.InputStream; 29 | import java.io.InputStreamReader; 30 | 31 | import javax.swing.JButton; 32 | import javax.swing.JDialog; 33 | import javax.swing.JFrame; 34 | import javax.swing.JScrollPane; 35 | import javax.swing.JTextArea; 36 | import javax.swing.SwingWorker; 37 | import javax.swing.KeyStroke; 38 | import javax.swing.JComponent; 39 | 40 | import de.haukerehfeld.quakeinjector.gui.OkayCancelApplyPanel; 41 | 42 | public class EngineOutputDialog extends JDialog { 43 | private final static String windowTitle = "Engine Output"; 44 | private final static String outputQIPrefix = "[Quake Injector]: "; 45 | 46 | private final InputStream engineOut; 47 | 48 | private final JTextArea output; 49 | 50 | public EngineOutputDialog(final JFrame parent, final InputStream engineOut) { 51 | super(parent, windowTitle, true); 52 | this.engineOut = engineOut; 53 | 54 | output = new JTextArea(30, 80); 55 | output.setLineWrap(true); 56 | 57 | JScrollPane panelScroll = new JScrollPane(output); 58 | panelScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); 59 | panelScroll.getViewport().setBackground(javax.swing.UIManager.getColor("TextPane.background")); 60 | add(panelScroll, BorderLayout.CENTER); 61 | 62 | final JButton close = new JButton("Close"); 63 | 64 | close.addActionListener(new ActionListener() { 65 | public void actionPerformed(ActionEvent e) { 66 | setVisible(false); 67 | dispose(); 68 | } 69 | }); 70 | 71 | add(new OkayCancelApplyPanel(close, null, null, false), BorderLayout.PAGE_END); 72 | 73 | addEscapeListener(this); // this is probably not how you do it... -Spirit 74 | 75 | new SwingWorker() { 76 | @Override 77 | public Void doInBackground() { 78 | try { 79 | InputStreamReader in = new InputStreamReader(engineOut); 80 | BufferedReader b = new BufferedReader(in); 81 | 82 | 83 | output.append(outputQIPrefix+"Starting engine...\n"); 84 | String line; 85 | int lineCount = 0; 86 | while ((line = b.readLine()) != null) { 87 | //append is thread safe 88 | output.append(line.trim() + "\n"); 89 | lineCount++; 90 | 91 | //scroll on the event thread 92 | javax.swing.SwingUtilities.invokeLater(new SwingWorker() { 93 | public Void doInBackground() { 94 | output.scrollRectToVisible(new Rectangle(0, output.getHeight(), 0, 10)); 95 | return null; 96 | } 97 | }); 98 | } 99 | if (lineCount == 0) { 100 | output.append(outputQIPrefix+"Done with no output.\n"); 101 | } 102 | else { 103 | output.append(outputQIPrefix+"Done.\n"); 104 | } 105 | 106 | } 107 | catch (java.io.IOException e) { 108 | output.append(outputQIPrefix+"Error: Couldn't read engine output from stream"); 109 | } 110 | 111 | return null; 112 | } 113 | @Override 114 | public void done() { 115 | } 116 | }.execute(); 117 | } 118 | 119 | // from http://stackoverflow.com/questions/642925/swing-how-do-i-close-a-dialog-when-the-esc-key-is-pressed -Spirit 120 | public static void addEscapeListener(final JDialog dialog) { 121 | ActionListener escListener = new ActionListener() { 122 | @Override 123 | public void actionPerformed(ActionEvent e) { 124 | dialog.dispose(); 125 | } 126 | }; 127 | 128 | dialog.getRootPane().registerKeyboardAction(escListener, 129 | KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), 130 | JComponent.WHEN_IN_FOCUSED_WINDOW); 131 | 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/EngineStarter.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | import java.io.File; 23 | import java.util.ArrayList; 24 | 25 | import javax.xml.parsers.DocumentBuilder; 26 | import javax.xml.parsers.DocumentBuilderFactory; 27 | import javax.xml.xpath.XPath; 28 | import javax.xml.xpath.XPathExpression; 29 | import javax.xml.xpath.XPathFactory; 30 | 31 | import org.w3c.dom.Document; 32 | 33 | public class EngineStarter { 34 | private File workingDir; 35 | private File quakeExe; 36 | private String quakeCmdline; 37 | 38 | private static boolean isMacOSX() { 39 | return System.getProperty("os.name").startsWith("Mac OS X"); 40 | } 41 | 42 | /** 43 | * Checks whether the given File is a Mac OS X application bundle. 44 | */ 45 | private static boolean isMacApplication(File app) { 46 | return app.isDirectory() 47 | && app.getName().endsWith(".app"); 48 | } 49 | 50 | /** 51 | * Checks whether exe is an executable file. 52 | */ 53 | private static boolean isExecutable(File exe) { 54 | return !exe.isDirectory() && exe.canExecute(); 55 | } 56 | 57 | /** 58 | * Checks whether app is a valid application. On Mac OS X it can 59 | * either be an executable or an app bundle, on other platforms 60 | * it must be an executable. 61 | */ 62 | public static boolean isValidApplication(File app) { 63 | if (!app.exists() || !app.canRead()) 64 | return false; 65 | 66 | if (isMacOSX() && isMacApplication(app)) 67 | return true; 68 | 69 | return isExecutable(app); 70 | } 71 | 72 | /** 73 | * Returns an error message to display when the user picks app 74 | * as the engine, or returns null if it is a valid application. 75 | * 76 | * @see #isValidApplication(File) 77 | */ 78 | public static String errorMessageForApplication(File app) { 79 | if (!app.exists()) { 80 | return "Doesn't exist!"; 81 | } 82 | 83 | if (isMacOSX()) { 84 | if (!isMacApplication(app) && !isExecutable(app)) { 85 | return "Must be an application or executable!"; 86 | } 87 | return null; 88 | 89 | } else { 90 | if (app.isDirectory()) { 91 | return "Must be an executable file!"; 92 | } 93 | else if (!app.canExecute()) { 94 | return "Cannot be executed!"; 95 | } 96 | return null; 97 | } 98 | } 99 | 100 | /** 101 | * If app is a Mac OS X app bundle (e.g. ~/quake/QuakeSpasm.app), returns 102 | * the executable inside the app bundle (e.g. ~/quake/QuakeSpasm.app/Contents/MacOS/QuakeSpasm). 103 | * Otherwise, returns app. 104 | */ 105 | private static File executableForApplication(File app) { 106 | if (app != null && isMacApplication(app)) { 107 | try { 108 | File contents = new File(app, "Contents"); 109 | File plist = new File(contents, "Info.plist"); 110 | File macOS = new File(contents, "MacOS"); 111 | 112 | DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 113 | Document document = builder.parse(plist); 114 | 115 | XPath xpath = XPathFactory.newInstance().newXPath(); 116 | XPathExpression expr = xpath.compile("/plist/dict/key/text()[.='CFBundleExecutable']/../following-sibling::string[1]/text()"); 117 | String cfBundleExecutable = (String) expr.evaluate(document); 118 | 119 | File executable = new File(macOS, cfBundleExecutable); 120 | return executable; 121 | } catch (Exception e) { 122 | e.printStackTrace(); 123 | return null; 124 | } 125 | } 126 | 127 | return app; 128 | } 129 | 130 | public EngineStarter(File workingDir, File quakeApp, Configuration.EngineCommandLine quakeCmdline) { 131 | this.workingDir = workingDir; 132 | setQuakeApplication(quakeApp); 133 | this.quakeCmdline = quakeCmdline.get(); 134 | } 135 | 136 | public Process start(String mapCmdline, String startmap) throws java.io.IOException { 137 | ArrayList cmd = new ArrayList(5); 138 | 139 | cmd.add(quakeExe.getAbsolutePath()); 140 | //processbuilder doesn't like arguments with spaces 141 | if (quakeCmdline != null) { 142 | for (String s: quakeCmdline.split(" ")) { cmd.add(s); } 143 | } 144 | if (mapCmdline != null) { 145 | for (String s: mapCmdline.split(" ")) { cmd.add(s); } 146 | } 147 | cmd.add("+map"); 148 | cmd.add(startmap); 149 | 150 | ProcessBuilder pb = new ProcessBuilder(cmd); 151 | pb.directory(workingDir); 152 | pb.redirectErrorStream(true); 153 | 154 | System.out.println(cmd); 155 | 156 | Process p = pb.start(); 157 | return p; 158 | } 159 | 160 | public void setWorkingDirectory(File dir) { 161 | this.workingDir = dir; 162 | } 163 | 164 | public void setQuakeApplication(File quakeApp) { 165 | this.quakeExe = executableForApplication(quakeApp); 166 | } 167 | 168 | public void setQuakeCommandline(Configuration.EngineCommandLine cmdline) { 169 | this.quakeCmdline = cmdline.get(); 170 | } 171 | 172 | public boolean checkPaths() { 173 | return (quakeExe.exists() 174 | && !quakeExe.isDirectory() 175 | && quakeExe.canRead() 176 | && quakeExe.canExecute()); 177 | } 178 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/FileInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | public class FileInfo implements Comparable { 23 | private String name; 24 | private long checksum; 25 | private boolean essential; 26 | 27 | public FileInfo(String name, long checksum) { 28 | this(name, checksum, true); 29 | } 30 | 31 | public FileInfo(String name, long checksum, boolean essential) { 32 | this.name = name; 33 | this.checksum = checksum; 34 | this.essential = essential; 35 | } 36 | 37 | /** 38 | * get checksum 39 | */ 40 | public long getChecksum() { return checksum; } 41 | 42 | /** 43 | * set checksum 44 | */ 45 | public void setChecksum(long checksum) { this.checksum = checksum; } 46 | 47 | /** 48 | * get name 49 | */ 50 | public String getName() { return name; } 51 | 52 | /** 53 | * set name 54 | */ 55 | public void setName(String name) { this.name = name; } 56 | 57 | /** 58 | * get essential 59 | */ 60 | public boolean getEssential() { return essential; } 61 | 62 | /** 63 | * set essential 64 | */ 65 | public void setEssential(boolean essential) { this.essential = essential; } 66 | 67 | 68 | public int compareTo(FileInfo o) { 69 | int i = o.getName().compareTo(getName()); 70 | if (i == 0) { 71 | i = Long.valueOf(o.getChecksum()).compareTo(Long.valueOf(getChecksum())); 72 | } 73 | return i; 74 | } 75 | 76 | public boolean equals(Object o) { 77 | return o instanceof FileInfo && ((FileInfo) o).getName().equals(getName()) && ((FileInfo) o).getChecksum() == getChecksum(); 78 | } 79 | 80 | public int hashCode() { 81 | return name.hashCode() + 17 * Long.valueOf(checksum).hashCode(); 82 | } 83 | 84 | public void clean() { 85 | name = name.replace('\\', '/'); 86 | } 87 | 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/FileNotWritableException.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | import java.io.FileNotFoundException; 23 | 24 | public class FileNotWritableException extends FileNotFoundException { 25 | public FileNotWritableException() { 26 | super(); 27 | } 28 | public FileNotWritableException(String message) { 29 | super(message); 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/HTTPException.java: -------------------------------------------------------------------------------- 1 | package de.haukerehfeld.quakeinjector; 2 | 3 | public class HTTPException extends RuntimeException { 4 | private final int statusCode; 5 | 6 | public HTTPException(int statusCode) { 7 | super(); 8 | this.statusCode = statusCode; 9 | } 10 | 11 | public int getStatusCode() { 12 | return statusCode; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/InspectZipWorker.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | import java.io.FileNotFoundException; 23 | import java.io.IOException; 24 | import java.io.InputStream; 25 | import java.io.BufferedInputStream; 26 | import java.util.List; 27 | import java.util.ArrayList; 28 | import java.util.zip.ZipEntry; 29 | import java.util.zip.ZipInputStream; 30 | 31 | import javax.swing.SwingWorker; 32 | 33 | /** 34 | * Inspect a zipfile and gather all zipentries 35 | */ 36 | public class InspectZipWorker extends SwingWorker, Void> { 37 | private InputStream input; 38 | 39 | public InspectZipWorker(InputStream input) { 40 | this.input = input; 41 | } 42 | 43 | @Override 44 | public List doInBackground() throws IOException, 45 | FileNotFoundException { 46 | ZipInputStream zis = new ZipInputStream(new BufferedInputStream(input)); 47 | List entries = new ArrayList(); 48 | 49 | ZipEntry entry; 50 | while((entry = zis.getNextEntry()) != null) { 51 | entries.add(entry); 52 | 53 | } 54 | zis.close(); 55 | 56 | return entries; 57 | } 58 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/InstallQueuePanel.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | import java.awt.Dimension; 23 | import java.awt.GridBagConstraints; 24 | import java.awt.GridBagLayout; 25 | import java.awt.Rectangle; 26 | import java.awt.event.ActionListener; 27 | import java.beans.PropertyChangeEvent; 28 | import java.beans.PropertyChangeListener; 29 | import java.util.ArrayDeque; 30 | import java.util.Queue; 31 | 32 | import javax.swing.JButton; 33 | import javax.swing.JLabel; 34 | import javax.swing.JPanel; 35 | import javax.swing.JProgressBar; 36 | import javax.swing.Scrollable; 37 | 38 | import de.haukerehfeld.quakeinjector.gui.ProgressPopup; 39 | 40 | 41 | public class InstallQueuePanel extends JPanel implements Scrollable { 42 | private final static int size = 5; 43 | private final static int rowHeight = 20; 44 | private final static int MARGIN = 3; 45 | 46 | private GridBagLayout layout = new GridBagLayout(); 47 | 48 | private Queue jobs = new ArrayDeque(); 49 | 50 | public InstallQueuePanel() { 51 | setLayout(layout); 52 | } 53 | 54 | /** 55 | * @return PropertyChangeListener that listens on "progress" for the progressbar 56 | */ 57 | public Job addJob(String description, ActionListener cancelAction) { 58 | Job progressListener = new Job(cancelAction, description); 59 | jobs.offer(progressListener); 60 | 61 | layoutComponents(); 62 | 63 | scrollRectToVisible(new Rectangle(0, getHeight(), 0, 100)); 64 | return progressListener; 65 | } 66 | 67 | private void layoutComponents() { 68 | int row = 0; 69 | for (Job j: jobs) { 70 | remove(j.progressBar); 71 | remove(j.cancelButton); 72 | remove(j.finishedLabel); 73 | //System.out.println("removing finishedlabel"); 74 | 75 | final int row_ = row; 76 | if (!j.finished) { 77 | add(j.progressBar, new ProgressBarConstraints() {{ gridy = row_; }}); 78 | add(j.cancelButton, new CancelButtonConstraints() {{ gridy = row_; }}); 79 | } 80 | else { 81 | add(j.finishedLabel, new FinishedLabelConstraints() {{ gridy = row_; }}); 82 | //System.out.println("adding finishedlabel"); 83 | } 84 | row++; 85 | } 86 | 87 | revalidate(); 88 | repaint(); 89 | } 90 | 91 | public void finished(final Job j, String message) { 92 | j.finish(message); 93 | 94 | layoutComponents(); 95 | } 96 | 97 | @Override 98 | public Dimension getPreferredScrollableViewportSize() { 99 | return getPreferredSize(); 100 | } 101 | 102 | @Override 103 | public int getScrollableUnitIncrement(Rectangle visibleRect, 104 | int orientation, 105 | int direction) { 106 | return rowHeight; 107 | } 108 | 109 | @Override 110 | public int getScrollableBlockIncrement(Rectangle visibleRect, 111 | int orientation, 112 | int direction) { 113 | return rowHeight; 114 | } 115 | 116 | @Override 117 | public boolean getScrollableTracksViewportWidth() { return true; } 118 | @Override 119 | public boolean getScrollableTracksViewportHeight() { return false; } 120 | 121 | private class RowConstraints extends GridBagConstraints {{ 122 | anchor = CENTER; 123 | fill = HORIZONTAL; 124 | insets = new java.awt.Insets(MARGIN, MARGIN, MARGIN, MARGIN); 125 | }} 126 | 127 | private class ProgressBarConstraints extends RowConstraints {{ 128 | weightx = 1; 129 | weighty = 1; 130 | }} 131 | 132 | private class CancelButtonConstraints extends RowConstraints {{ 133 | gridx = 1; 134 | }} 135 | 136 | private class FinishedLabelConstraints extends RowConstraints {{ 137 | weightx = 1; 138 | gridwidth = 2; 139 | }} 140 | 141 | public static class Job implements PropertyChangeListener { 142 | private JProgressBar progressBar; 143 | private JButton cancelButton; 144 | private JLabel finishedLabel; 145 | 146 | private String description; 147 | 148 | private boolean finished = false; 149 | 150 | public Job(ActionListener cancelAction, String description) { 151 | this.description = description; 152 | 153 | progressBar = new JProgressBar(); 154 | progressBar.setString(ProgressPopup.progressString(description, 0)); 155 | progressBar.setValue(0); 156 | progressBar.setStringPainted(true); 157 | 158 | cancelButton = new JButton("Cancel"); 159 | cancelButton.addActionListener(cancelAction); 160 | 161 | finishedLabel = new JLabel(description); 162 | } 163 | 164 | public void propertyChange(PropertyChangeEvent evt) { 165 | if ("progress" == evt.getPropertyName()) { 166 | int p = (Integer) evt.getNewValue(); 167 | setProgress(p); 168 | } 169 | } 170 | 171 | public void setProgress(int progress) { 172 | progressBar.setString(ProgressPopup.progressString(description, progress)); 173 | progressBar.setValue(progress); 174 | } 175 | 176 | private void finish(String message) { 177 | finished = true; 178 | progressBar.setEnabled(false); 179 | cancelButton.setEnabled(false); 180 | finishedLabel.setText(ProgressPopup.progressString(description, message)); 181 | finishedLabel.setPreferredSize(new Dimension((int) finishedLabel 182 | .getPreferredSize().getWidth(), 183 | (int) cancelButton.getSize().getHeight())); 184 | 185 | } 186 | } 187 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/InstallWorker.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | import java.io.BufferedInputStream; 23 | import java.io.File; 24 | import java.io.FileNotFoundException; 25 | import java.io.IOException; 26 | import java.io.InputStream; 27 | import java.util.List; 28 | import java.util.zip.ZipEntry; 29 | import java.util.zip.ZipInputStream; 30 | 31 | import javax.swing.SwingWorker; 32 | 33 | /** 34 | * Install maps in a worker thread 35 | * Init once and let swing start it - don't reuse 36 | */ 37 | public class InstallWorker extends SwingWorker implements 38 | ProgressListener, 39 | Cancelable { 40 | private final static int BUFFERSIZE = 1024; 41 | 42 | private File baseDirectory; 43 | private String unzipDirectory; 44 | private Package map; 45 | private InputStream input; 46 | private List overwrites; 47 | 48 | private long downloadSize = 0; 49 | 50 | /** 51 | * files that got installed 52 | */ 53 | private PackageFileList files; 54 | 55 | /** 56 | * @param inputSize size of the input stream in bytes, for progress reporting 57 | * @param overwrites a list of files that we should overwrite, or 58 | * null if everything should be overwritten 59 | */ 60 | public InstallWorker(InputStream input, 61 | long inputSize, 62 | Package map, 63 | File baseDirectory, 64 | String unzipDirectory, 65 | List overwrites) { 66 | this.map = map; 67 | this.input = input; 68 | this.downloadSize = inputSize; 69 | this.baseDirectory = baseDirectory; 70 | this.unzipDirectory = unzipDirectory; 71 | this.files = new PackageFileList(map.getId()); 72 | this.overwrites = overwrites; 73 | } 74 | 75 | @Override 76 | public PackageFileList doInBackground() throws IOException, 77 | FileNotFoundException, 78 | Installer.CancelledException { 79 | System.out.println("Installing " + map.getId()); 80 | 81 | unzip(input, 82 | baseDirectory, 83 | unzipDirectory, 84 | map.getId(), 85 | overwrites); 86 | 87 | map.setInstalled(true); 88 | return files; 89 | } 90 | 91 | 92 | /** 93 | * Unzip from the inputstream to the quake base dir 94 | */ 95 | public void unzip(InputStream in, 96 | File basedir, 97 | String unzipdir, 98 | String mapid, 99 | List overwrites) 100 | throws IOException, FileNotFoundException, Installer.CancelledException { 101 | //build progress filter chain 102 | ProgressListener progress = 103 | new SumProgressListener( 104 | new PercentageProgressListener(downloadSize, 105 | new CheckCanceledProgressListener(this, 106 | this))); 107 | 108 | 109 | ZipInputStream zis = new ZipInputStream(new BufferedInputStream(in)); 110 | ZipEntry entry; 111 | 112 | boolean extracted = false; 113 | while((entry = zis.getNextEntry()) != null) { 114 | File f = new File(unzipdir + File.separator + entry.getName()); 115 | String filename = RelativePath.getRelativePath(basedir, f).toString(); 116 | 117 | if (overwrites != null && overwrites.indexOf(f) < 0) { 118 | System.out.println("Skipping " + filename + ", because it isn't supposed to be overwritten."); 119 | continue; 120 | } 121 | 122 | //create dirs 123 | List createdDirs = Utils.mkdirs(f); 124 | 125 | //do nothing for directories other than creating them 126 | if (!entry.isDirectory()) { 127 | File original = f; 128 | if (f.exists()) { 129 | //create Temp file and rename later 130 | f = File.createTempFile("quakeinjector", ".tmp", f.getParentFile()); 131 | System.out.println("create Temp file " + f); 132 | } 133 | 134 | System.out.println("Writing " + filename + " (" + entry.getCompressedSize() + "b)"); 135 | 136 | long crc; 137 | try { 138 | crc = Utils.writeFile(zis, 139 | f, 140 | new CompressedProgressListener(entry.getCompressedSize() 141 | / (double) entry.getSize(), 142 | progress)); 143 | } 144 | catch (FileNotFoundException e) { 145 | throw new FileNotWritableException(e.getMessage()); 146 | } 147 | 148 | if (crc != entry.getCrc()) { 149 | System.err.println("Crc32 didn't match on extraction of " + original + ", removing..."); 150 | f.delete(); 151 | continue; 152 | } 153 | 154 | /** @todo 2009-12-19 03:03 hrehfeld add crc calculation */ 155 | FileInfo info = new FileInfo(filename, crc); 156 | files.add(info); 157 | 158 | 159 | for (File dirname: createdDirs) { 160 | //save relative paths to files so we can delete dirs later 161 | files.add(new FileInfo(RelativePath.getRelativePath(basedir, dirname).toString(), 0)); 162 | } 163 | 164 | //if we extracted to temp, rename 165 | if (!f.equals(original)) { 166 | original.delete(); 167 | System.out.println("moving Temp file to " + original); 168 | f.renameTo(original); 169 | } 170 | 171 | extracted = true; 172 | } 173 | } 174 | 175 | if (!extracted) { 176 | throw new java.util.zip.ZipException("No files extracted from zip, is it an empty file?"); 177 | } 178 | zis.close(); 179 | } 180 | 181 | public void publish(long progress) { 182 | if (progress <= 100) { 183 | setProgress((int) progress); 184 | } 185 | } 186 | 187 | public void checkCancelled() throws Installer.CancelledException { 188 | if (isCancelled()) { 189 | System.out.println("canceling..."); 190 | throw new Installer.CancelledException(); 191 | } 192 | } 193 | 194 | public PackageFileList getInstalledFiles() { 195 | return files; 196 | } 197 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/InstalledPackageList.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | import java.io.InputStream; 23 | import java.io.OutputStream; 24 | import java.util.ArrayList; 25 | import java.util.Collections; 26 | import java.util.HashMap; 27 | import java.util.List; 28 | import java.util.Map; 29 | 30 | import javax.xml.parsers.DocumentBuilder; 31 | import javax.xml.parsers.DocumentBuilderFactory; 32 | import javax.xml.transform.OutputKeys; 33 | import javax.xml.transform.Transformer; 34 | import javax.xml.transform.TransformerFactory; 35 | import javax.xml.transform.dom.DOMSource; 36 | import javax.xml.transform.stream.StreamResult; 37 | 38 | import org.w3c.dom.Document; 39 | import org.w3c.dom.Element; 40 | import org.w3c.dom.Node; 41 | import org.w3c.dom.NodeList; 42 | 43 | public class InstalledPackageList { 44 | private final static String ROOTNODE = "maps"; 45 | 46 | public void write(OutputStream out, Iterable list) 47 | throws java.io.IOException { 48 | Map> files = new HashMap>(); 49 | 50 | for (Requirement r: list) { 51 | if (!r.isInstalled()) { 52 | continue; 53 | } 54 | 55 | Iterable l = r.getFileList(); 56 | if (l == null) { 57 | l = Collections.emptyList(); 58 | } 59 | 60 | files.put(r.getId(), l); 61 | } 62 | if (files.isEmpty()) { 63 | System.out.println("WARNING: writing empty maplist"); 64 | } 65 | write(out, files); 66 | } 67 | 68 | public void write(OutputStream out, Map> files) 69 | throws java.io.IOException { 70 | 71 | //try { 72 | DocumentBuilder docBuilder = null; 73 | try { 74 | docBuilder 75 | = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 76 | } 77 | catch (javax.xml.parsers.ParserConfigurationException e) { 78 | System.err.println("Couldn't instantiate Document Builder: " + e); 79 | e.printStackTrace(); 80 | } 81 | Document doc = docBuilder.newDocument(); 82 | 83 | //create the root element and add it to the document 84 | Element root = doc.createElement(ROOTNODE); 85 | doc.appendChild(root); 86 | 87 | for (Map.Entry> e: files.entrySet()) { 88 | String id = e.getKey(); 89 | Iterable fileIterable = e.getValue(); 90 | 91 | Element mapNode = doc.createElement("map"); 92 | mapNode.setAttribute("id", id); 93 | root.appendChild(mapNode); 94 | 95 | for (FileInfo file: fileIterable) { 96 | Element fileNode = doc.createElement("file"); 97 | //System.out.println("adding node " + file.getName()); 98 | fileNode.setAttribute("name", file.getName()); 99 | long crc = file.getChecksum(); 100 | if (crc != 0) { 101 | fileNode.setAttribute("crc", Long.toString(crc)); 102 | } 103 | 104 | if (!file.getEssential()) { 105 | fileNode.setAttribute("essential", Boolean.toString(false)); 106 | } 107 | 108 | mapNode.appendChild(fileNode); 109 | } 110 | } 111 | 112 | //Output the XML 113 | 114 | //set up a transformer 115 | Transformer trans = null; 116 | try { 117 | trans = TransformerFactory.newInstance().newTransformer(); 118 | } 119 | catch (javax.xml.transform.TransformerConfigurationException e) { 120 | System.err.println(e); 121 | e.printStackTrace(); 122 | } 123 | 124 | trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); 125 | trans.setOutputProperty(OutputKeys.INDENT, "yes"); 126 | 127 | //create string from xml tree 128 | StreamResult result = new StreamResult(out); 129 | DOMSource source = new DOMSource(doc); 130 | try { 131 | trans.transform(source, result); 132 | } 133 | catch (javax.xml.transform.TransformerException e) { 134 | } 135 | } 136 | 137 | public List read(InputStream in) throws java.io.IOException { 138 | Document document; 139 | try { 140 | DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 141 | DocumentBuilder db = dbf.newDocumentBuilder(); 142 | 143 | document = db.parse(in); 144 | } 145 | catch (javax.xml.parsers.ParserConfigurationException e) { 146 | throw new java.io.IOException("Couldn't parse installed package list: " 147 | + e.getMessage()); 148 | } 149 | catch (org.xml.sax.SAXException e) { 150 | throw new java.io.IOException("Couldn't parse Installed Package List: " 151 | + e.getMessage()); 152 | } 153 | 154 | Element root = document.getDocumentElement(); 155 | 156 | NodeList installedMaps = root.getChildNodes(); 157 | 158 | 159 | List files = new ArrayList(installedMaps.getLength()); 160 | 161 | for (int i = 0; i < installedMaps.getLength(); ++i) { 162 | Node map = installedMaps.item(i); 163 | 164 | if (map.getNodeType() == Node.ELEMENT_NODE) { 165 | PackageFileList l = parseMapFileList((Element) map); 166 | files.add(l); 167 | } 168 | /** @todo 2009-03-29 01:36 hrehfeld find out why this happens */ 169 | else { 170 | // System.out.println("node: " + file.getNodeName()); 171 | // System.out.println("Whoops, i thought file is an element!"); 172 | } 173 | } 174 | 175 | return files; 176 | } 177 | 178 | private PackageFileList parseMapFileList(Element map) { 179 | String id = map.getAttribute("id"); 180 | PackageFileList fileList = new PackageFileList(id); 181 | 182 | NodeList files = map.getChildNodes(); 183 | 184 | for (int i = 0; i < files.getLength(); ++i) { 185 | Node file = files.item(i); 186 | if (file.getNodeType() == Node.ELEMENT_NODE) { 187 | Element e = (Element) file; 188 | String name = e.getAttribute("name"); 189 | 190 | long crc = 0; 191 | if (e.hasAttribute("crc")) { 192 | crc = Long.parseLong(e.getAttribute("crc")); 193 | } 194 | boolean essential = true; 195 | if (e.hasAttribute("essential") && e.getAttribute("essential").equals(Boolean.toString(false))) { 196 | essential = false; 197 | } 198 | fileList.add(new FileInfo(name, crc, essential)); 199 | } 200 | } 201 | 202 | return fileList; 203 | 204 | } 205 | } 206 | 207 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/InstalledPackages.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | import java.util.concurrent.Future; 23 | import java.util.concurrent.ExecutionException; 24 | import java.util.List; 25 | import java.util.Collections; 26 | import java.io.InputStream; 27 | import java.io.BufferedInputStream; 28 | import java.io.FileInputStream; 29 | import java.io.File; 30 | import java.io.FileNotFoundException; 31 | import java.io.IOException; 32 | 33 | import javax.swing.SwingWorker; 34 | 35 | public class InstalledPackages { 36 | private Future> installedPackages; 37 | 38 | public void parse(File installedPackagesXml) { 39 | ParseInstalledPackagesWorker parseInstalled 40 | = new ParseInstalledPackagesWorker(installedPackagesXml); 41 | parseInstalled.execute(); 42 | installedPackages = parseInstalled; 43 | } 44 | 45 | public List get() throws InterruptedException, ExecutionException { 46 | try { 47 | return installedPackages.get(); 48 | } 49 | catch (InterruptedException e) { 50 | throw e; 51 | } 52 | catch (ExecutionException e) { 53 | throw e; 54 | } 55 | } 56 | 57 | 58 | public List getDefaultErrorHandling() { 59 | try { 60 | return get(); 61 | } 62 | catch (InterruptedException e) { 63 | System.err.println("Error: couldn't set installedPackages, interrupted!" + e); 64 | e.printStackTrace(); 65 | } 66 | catch (ExecutionException err) { 67 | try { 68 | throw err.getCause(); 69 | } 70 | catch (java.io.FileNotFoundException e) { 71 | System.out.println("Notice: installed packages file doesn't exist yet," 72 | + " no packages installed? " + e); 73 | } 74 | catch (java.io.IOException e) { 75 | System.err.println("Error: installed packages file couldn't be " 76 | + "loaded: " + e); 77 | e.printStackTrace(); 78 | } 79 | } 80 | finally { 81 | return Collections.emptyList(); 82 | } 83 | } 84 | 85 | 86 | class ParseInstalledPackagesWorker extends SwingWorker,Void> { 87 | public final File xml; 88 | 89 | public ParseInstalledPackagesWorker(File xml) { 90 | this.xml = xml; 91 | } 92 | 93 | @Override 94 | public List doInBackground() throws FileNotFoundException, IOException { 95 | return parseInstalledPackagesFile(xml); 96 | } 97 | } 98 | 99 | /** 100 | * Thrown if no installed packages xml exists 101 | */ 102 | public static class NoInstalledPackagesFileException extends FileNotFoundException { 103 | public NoInstalledPackagesFileException(String msg) { super(msg); } 104 | public NoInstalledPackagesFileException() { super(); } 105 | } 106 | 107 | private List parseInstalledPackagesFile(File installedPackagesFile) 108 | throws NoInstalledPackagesFileException, FileNotFoundException, IOException { 109 | if (!installedPackagesFile.exists()) { 110 | throw new NoInstalledPackagesFileException("No Installed packages file (" + installedPackagesFile + ")" 111 | + ", no packages installed?"); 112 | } 113 | 114 | final InputStream in = new BufferedInputStream(new FileInputStream(installedPackagesFile)); 115 | 116 | List result = new InstalledPackageList().read(in); 117 | 118 | //close the stream after parsing is finished 119 | Exception error = null; 120 | try { 121 | in.close(); 122 | } 123 | catch (java.io.FileNotFoundException e) { 124 | error = e; 125 | } 126 | catch (java.io.IOException e) { 127 | error = e; 128 | } 129 | if (error != null ) { 130 | System.err.println("Error: couldn't close filestream " + installedPackagesFile + ": " + error); 131 | } 132 | return result; 133 | } 134 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/Menu.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | //import java.awt.*; 23 | import java.awt.event.ActionEvent; 24 | import java.awt.event.ActionListener; 25 | import java.awt.event.KeyEvent; 26 | 27 | import javax.swing.JMenu; 28 | import javax.swing.JMenuBar; 29 | import javax.swing.JMenuItem; 30 | import javax.swing.JCheckBoxMenuItem; 31 | import javax.swing.KeyStroke; 32 | 33 | public class Menu extends JMenuBar { 34 | private JCheckBoxMenuItem enableOfflineMode; 35 | 36 | public Menu(ActionListener databaseParser, 37 | ActionListener checkForInstalledMaps, 38 | ActionListener quitter, 39 | ActionListener showEngineConfig, 40 | ActionListener offlineAction) { 41 | setOpaque(true); 42 | // setPreferredSize(new Dimension(200, 20)); 43 | 44 | JMenu fileMenu = new JMenu("File"); 45 | add(fileMenu); 46 | 47 | JMenuItem reparseDatabase = new JMenuItem("Reload database", KeyEvent.VK_R); 48 | reparseDatabase.addActionListener(databaseParser); 49 | fileMenu.add(reparseDatabase); 50 | 51 | JMenuItem checkInstalled = new JMenuItem("Check for installed maps (experimental!)", KeyEvent.VK_C); 52 | checkInstalled.addActionListener(checkForInstalledMaps); 53 | fileMenu.add(checkInstalled); 54 | 55 | enableOfflineMode = new JCheckBoxMenuItem("Offline Mode"); 56 | checkInstalled.addActionListener(offlineAction); 57 | fileMenu.add(enableOfflineMode); 58 | 59 | 60 | JMenuItem quit = new JMenuItem("Quit", KeyEvent.VK_T); 61 | quit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_1, ActionEvent.ALT_MASK)); 62 | quit.getAccessibleContext().setAccessibleDescription("This doesn't really do anything"); 63 | quit.addActionListener(quitter); 64 | fileMenu.add(quit); 65 | 66 | JMenu configM = new JMenu("Configuration"); 67 | add(configM); 68 | 69 | JMenuItem engine = new JMenuItem("Engine Configuration"); 70 | configM.add(engine); 71 | engine.addActionListener(showEngineConfig); 72 | } 73 | 74 | public void setOfflineMode(boolean offline) { 75 | enableOfflineMode.setSelected(offline); 76 | } 77 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/OnlineFileNotFoundException.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | import java.io.FileNotFoundException; 23 | 24 | public class OnlineFileNotFoundException extends FileNotFoundException { 25 | public OnlineFileNotFoundException() { 26 | super(); 27 | } 28 | public OnlineFileNotFoundException(String message) { 29 | super(message); 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/Package.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | import java.util.ArrayList; 23 | import java.util.Date; 24 | import java.util.List; 25 | 26 | import javax.swing.event.ChangeListener; 27 | 28 | /** 29 | * A requirement that has an installation candidate available. 30 | */ 31 | public class Package extends SortableRequirement implements Requirement { 32 | 33 | /** 34 | * easily have change listeners 35 | */ 36 | private ChangeListenerList listeners = new ChangeListenerList(); 37 | 38 | private String author; 39 | 40 | private String title; 41 | 42 | private Rating rating; 43 | 44 | private float normalizedUsersRating; 45 | 46 | private String description; 47 | 48 | /** 49 | * Size in kb? 50 | */ 51 | private int size; 52 | 53 | private Date date; 54 | 55 | private String relativeBaseDir; 56 | 57 | private String commandline; 58 | 59 | private List startmaps; 60 | 61 | private List requirements; 62 | 63 | private PackageFileList fileList; 64 | private PackageFileList supposedFileList; 65 | 66 | public Package(String id, 67 | String author, 68 | String title, 69 | int size, 70 | Date date, 71 | boolean isInstalled, 72 | Rating rating, 73 | float normalizedUsersRating, 74 | String description) { 75 | this(id, author, title, size, date, isInstalled, rating, normalizedUsersRating, description, null, null, null, null); 76 | } 77 | 78 | public Package(String id, 79 | String author, 80 | String title, 81 | int size, 82 | Date date, 83 | boolean isInstalled, 84 | Rating rating, 85 | float normalizedUsersRating, 86 | String description, 87 | String relativeBaseDir, 88 | String commandline, 89 | List startmaps, 90 | List requirements) { 91 | super(id); 92 | this.author = author; 93 | this.title = title; 94 | this.size = size; 95 | this.date = date; 96 | super.setInstalled(isInstalled); 97 | this.rating = rating; 98 | this.normalizedUsersRating = normalizedUsersRating; 99 | this.description = description; 100 | this.relativeBaseDir = relativeBaseDir; 101 | this.commandline = commandline; 102 | this.startmaps = startmaps; 103 | this.requirements = requirements; 104 | } 105 | 106 | @Override 107 | public void addChangeListener(ChangeListener l) { 108 | listeners.addChangeListener(l); 109 | } 110 | 111 | 112 | @Override 113 | public void removeChangeListener(ChangeListener l) { 114 | listeners.removeChangeListener(l); 115 | } 116 | 117 | public String getAuthor() { 118 | return author; 119 | } 120 | public String getTitle() { 121 | return title; 122 | } 123 | public int getSize() { 124 | return size; 125 | } 126 | public Date getDate() { 127 | return date; 128 | } 129 | 130 | /** 131 | * get rating 132 | */ 133 | public Rating getRating() { return rating; } 134 | 135 | public float getNormalizedUsersRating() { return normalizedUsersRating; } 136 | 137 | 138 | /** 139 | * get description 140 | */ 141 | public String getDescription() { return description; } 142 | 143 | 144 | public String getRelativeBaseDir() { 145 | return relativeBaseDir; 146 | } 147 | 148 | public String getCommandline() { 149 | return commandline; 150 | } 151 | 152 | public List getStartmaps() { 153 | return startmaps; 154 | } 155 | 156 | public void setRequirements(List requirements) { 157 | this.requirements = requirements; 158 | } 159 | 160 | public List getRequirements() { 161 | return this.requirements; 162 | } 163 | 164 | 165 | public List getAvailableRequirements() { 166 | List avails = new ArrayList(); 167 | for (Requirement r: requirements) { 168 | if (r instanceof Package) { 169 | avails.add((Package) r); 170 | } 171 | } 172 | return avails; 173 | } 174 | 175 | 176 | public List getUnavailableRequirements() { 177 | List unavails = new ArrayList(); 178 | 179 | for (Requirement r: requirements) { 180 | if (!r.isInstalled() && !(r instanceof Package)) { 181 | unavails.add(r); 182 | } 183 | } 184 | return unavails; 185 | } 186 | 187 | public List getUnmetRequirements() { 188 | List unmet = new ArrayList(); 189 | for (Requirement requirement: requirements) { 190 | if (!requirement.isInstalled()) { 191 | unmet.add(requirement); 192 | } 193 | } 194 | 195 | return unmet; 196 | } 197 | 198 | @Override 199 | protected void notifyChangeListeners() { 200 | listeners.notifyChangeListeners(this); 201 | } 202 | 203 | @Override 204 | public String toString() { 205 | return getId() + " (" + isInstalled() + ")"; 206 | } 207 | 208 | /** 209 | * get fileList 210 | */ 211 | public PackageFileList getFileList() { return fileList; } 212 | 213 | /** 214 | * set fileList 215 | */ 216 | public void setFileList(PackageFileList fileList) { this.fileList = fileList; } 217 | 218 | /** 219 | * get supposedFileList 220 | */ 221 | public PackageFileList getSupposedFileList() { return supposedFileList; } 222 | 223 | /** 224 | * set supposedFileList 225 | */ 226 | public void setSupposedFileList(PackageFileList supposedFileList) { this.supposedFileList = supposedFileList; } 227 | 228 | public static enum Rating { 229 | Unrated(0), 230 | Crap(1), 231 | Poor(2), 232 | Average(3), 233 | Nice(4), 234 | Excellent(5); 235 | 236 | private int rating; 237 | Rating(int rating) { 238 | this.rating = rating; 239 | } 240 | 241 | public int getRating() { 242 | return rating; 243 | } 244 | } 245 | } 246 | 247 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/PackageDatabaseParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | import java.io.IOException; 23 | import java.util.ArrayList; 24 | import java.util.Date; 25 | import java.util.GregorianCalendar; 26 | import java.util.HashMap; 27 | import java.util.List; 28 | import java.util.Map; 29 | 30 | import org.w3c.dom.Document; 31 | import org.w3c.dom.Element; 32 | import org.w3c.dom.Node; 33 | 34 | import de.haukerehfeld.quakeinjector.Package.Rating; 35 | 36 | public class PackageDatabaseParser implements java.io.Serializable { 37 | private final static Rating[] ratingTable = { Rating.Unrated, 38 | Rating.Crap, 39 | Rating.Poor, 40 | Rating.Average, 41 | Rating.Nice, 42 | Rating.Excellent 43 | }; 44 | 45 | 46 | /** 47 | * Parse the complete document 48 | */ 49 | public List parse(Document document) throws IOException, 50 | org.xml.sax.SAXException { 51 | HashMap packages = new HashMap(); 52 | Map> unresolvedRequirements = new HashMap>(); 53 | 54 | Element files = document.getDocumentElement(); 55 | 56 | for (Node file: XmlUtils.iterate(files.getChildNodes())) { 57 | if (XmlUtils.isElement(file)) { 58 | Package currentPackage = parsePackage((Element) file, unresolvedRequirements); 59 | packages.put(currentPackage.getId(), currentPackage); 60 | } 61 | /** @todo 2009-03-29 01:36 hrehfeld find out why this happens */ 62 | else { 63 | // System.out.println("node: " + file.getNodeName()); 64 | // System.out.println("Whoops, i thought file is an element!"); 65 | } 66 | } 67 | 68 | resolveRequirements(unresolvedRequirements, packages); 69 | 70 | List packageList = new ArrayList(packages.values()); 71 | return packageList; 72 | } 73 | 74 | private void resolveRequirements(Map> unresolvedRequirements, 75 | Map packages) { 76 | for (Map.Entry> entry: unresolvedRequirements.entrySet()) { 77 | Package current = entry.getKey(); 78 | List reqs = entry.getValue(); 79 | 80 | // if (reqs.size() > 1) { 81 | // System.out.println(current.getId() + " has more than one requirement"); 82 | // } 83 | 84 | List resolvedRequirements = new ArrayList(reqs.size()); 85 | for (String id: reqs) { 86 | Requirement resolved = packages.get(id); 87 | if (resolved == null) { 88 | resolved = new UnavailableRequirement(id); 89 | packages.put(id, resolved); 90 | } 91 | resolvedRequirements.add(resolved); 92 | } 93 | current.setRequirements(resolvedRequirements); 94 | } 95 | } 96 | 97 | 98 | /** 99 | * Parse a single Package/file entry 100 | */ 101 | private Package parsePackage(Element file, Map> reqResolve) { 102 | String id = file.getAttribute("id"); 103 | Rating rating; 104 | { 105 | String ratingString = file.getAttribute("rating"); 106 | int ratingNumber = 0; 107 | if (!ratingString.equals("")) { 108 | try { 109 | ratingNumber = Integer.parseInt(ratingString); 110 | 111 | if (ratingNumber < 0 || ratingNumber > 5) { 112 | System.out.println("Rating of " + id + " is broken"); 113 | ratingNumber = 0; 114 | } 115 | } 116 | catch (java.lang.NumberFormatException e) { 117 | System.out.println("Rating of " + id + " is broken"); 118 | } 119 | } 120 | rating = ratingTable[ratingNumber]; 121 | } 122 | 123 | float normalizedUsersRating; 124 | { 125 | String normalizedUsersRatingString = file.getAttribute("normalized_users_rating"); 126 | try { 127 | normalizedUsersRating = Float.parseFloat(normalizedUsersRatingString); 128 | } catch (java.lang.NumberFormatException e) { 129 | System.out.println("Users' rating of " + id + " is broken"); 130 | normalizedUsersRating = Float.NEGATIVE_INFINITY; 131 | } 132 | } 133 | 134 | String title = XmlUtils.getFirstElement(file, "title").getTextContent().trim(); 135 | String author = XmlUtils.getFirstElement(file, "author").getTextContent().trim(); 136 | int size; 137 | try { 138 | size = Integer.parseInt(XmlUtils.getFirstElement(file, "size").getTextContent().trim()); 139 | } 140 | catch (java.lang.NumberFormatException e) { 141 | System.out.println("XML Parsing Error: malformed tag on record \"" + id + "\""); 142 | size = 0; 143 | } 144 | 145 | String date = XmlUtils.getFirstElement(file, "date").getTextContent().trim(); 146 | 147 | String description = XmlUtils.getTextForNode(XmlUtils.getFirstElement(file, "description")) 148 | .trim(); 149 | 150 | 151 | String relativeBaseDir = null; 152 | String cmdline = null; 153 | ArrayList startmaps = new ArrayList(); 154 | 155 | ArrayList requirements = new ArrayList(); 156 | 157 | // parse techinfo stuff 158 | Element techinfo = XmlUtils.getFirstElement(file, "techinfo"); 159 | if (techinfo != null) { 160 | for (Node node: XmlUtils.iterate(techinfo.getChildNodes())) { 161 | if (!XmlUtils.isElement(node)) { 162 | continue; 163 | } 164 | Element info = (Element) node; 165 | 166 | if (info.getTagName().equals("zipbasedir")) { 167 | relativeBaseDir = info.getTextContent(); 168 | } 169 | else if (info.getTagName().equals("commandline")) { 170 | cmdline = info.getTextContent(); 171 | } 172 | else if (info.getTagName().equals("startmap")) { 173 | startmaps.add(info.getTextContent()); 174 | } 175 | else if (info.getTagName().equals("requirements")) { 176 | for (Node reqFile: XmlUtils.iterate(info.getChildNodes())) { 177 | if (XmlUtils.isElement(reqFile)) { 178 | String r = ((Element) reqFile).getAttribute("id"); 179 | requirements.add(r); 180 | } 181 | } 182 | } 183 | } 184 | } 185 | 186 | // System.out.println("id: " + id 187 | // + ", title: " + title 188 | // + ", size: " + size); 189 | 190 | //if there's no startmap tag, use the id 191 | if (startmaps.isEmpty()) { 192 | startmaps.add(id); 193 | } 194 | 195 | Package result = new Package(id, 196 | author, 197 | title, 198 | size, 199 | PackageDatabaseParser.parseDate(date), 200 | false, 201 | rating, 202 | normalizedUsersRating, 203 | description, 204 | relativeBaseDir, 205 | cmdline, 206 | startmaps, 207 | null); 208 | reqResolve.put(result, requirements); 209 | return result; 210 | } 211 | 212 | /** 213 | * Parses the date that's in dd.mm.yyyy format 214 | */ 215 | public static Date parseDate(String date) { 216 | String[] components = date.split("\\."); 217 | if (components.length < 3) { 218 | throw new RuntimeException("Xml Parsing error: date malformed"); 219 | } 220 | int day = Integer.parseInt(components[0]); 221 | int month = Integer.parseInt(components[1]) - 1; 222 | int year = Integer.parseInt(components[2]); 223 | return new GregorianCalendar(year, month, day).getTime(); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/PackageDatabaseParserWorker.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | //import java.awt.*; 23 | import java.io.InputStream; 24 | import java.util.List; 25 | 26 | import javax.swing.SwingWorker; 27 | 28 | /** 29 | * Parse database xml and set database in background 30 | */ 31 | public class PackageDatabaseParserWorker extends SwingWorker, Void> 32 | implements ProgressListener { 33 | private final String databaseUrl; 34 | 35 | public PackageDatabaseParserWorker(String databaseUrl) { 36 | this.databaseUrl = databaseUrl; 37 | } 38 | 39 | @Override 40 | public List doInBackground() throws java.io.IOException, org.xml.sax.SAXException { 41 | InputStream dl = getDownloadStream(this.databaseUrl); 42 | 43 | final PackageDatabaseParser parser = new PackageDatabaseParser(); 44 | List all = parser.parse(XmlUtils.getDocument(dl)); 45 | 46 | return all; 47 | } 48 | 49 | 50 | @Override 51 | public void done() { 52 | } 53 | 54 | private InputStream getDownloadStream(final String databaseUrl) throws java.io.IOException { 55 | //get download stream 56 | Download d = Download.create(databaseUrl); 57 | d.connect(); 58 | int size = d.getSize(); 59 | InputStream dl; 60 | if (size > 0) { 61 | ProgressListener progress = 62 | new SumProgressListener(new PercentageProgressListener(size, this)); 63 | dl = d.getStream(progress); 64 | } 65 | else { 66 | dl = d.getStream(null); 67 | } 68 | return dl; 69 | } 70 | 71 | 72 | public void publish(long progress) { 73 | if (progress <= 100) { 74 | setProgress((int) progress); 75 | } 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/PackageFileList.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | import java.util.Iterator; 23 | import java.util.TreeSet; 24 | 25 | public class PackageFileList implements Iterable, Comparable { 26 | /** 27 | * other classes rely on the sorted iteration this provides 28 | */ 29 | private TreeSet files = new TreeSet(); 30 | 31 | private String id; 32 | 33 | public PackageFileList(String id) { 34 | this.id = id; 35 | } 36 | 37 | public boolean isEmpty() { 38 | return files.isEmpty(); 39 | } 40 | 41 | public void add(FileInfo file) { 42 | file.clean(); 43 | files.add(file); 44 | } 45 | 46 | public void remove(FileInfo file) { 47 | file.clean(); 48 | files.remove(file); 49 | } 50 | 51 | public int size() { 52 | return files.size(); 53 | } 54 | 55 | /** 56 | * iterate the files in ascending order by their filename 57 | */ 58 | public Iterator iterator() { 59 | return files.iterator(); 60 | } 61 | 62 | public Iterator descendingIterator() { 63 | return files.descendingIterator(); 64 | } 65 | 66 | public String getId() { 67 | return id; 68 | } 69 | 70 | @Override public int compareTo(PackageFileList o) { 71 | return -(o.id.compareTo(id)); 72 | } 73 | @Override public boolean equals(Object o) { 74 | if (!(o instanceof PackageFileList)) { 75 | return false; 76 | } 77 | return ((PackageFileList) o).id.equals(id); 78 | } 79 | @Override public int hashCode() { 80 | return id.hashCode(); 81 | } 82 | 83 | 84 | @Override public String toString() { 85 | return id + "[" + files.toString() + "]"; 86 | } 87 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/PackageList.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | import java.util.ArrayList; 23 | import java.util.Iterator; 24 | import java.util.List; 25 | 26 | import javax.swing.event.ChangeEvent; 27 | import javax.swing.event.ChangeListener; 28 | 29 | /** 30 | * Only the Package s in a RequirementsList 31 | */ 32 | public class PackageList implements Iterable, ChangeListener { 33 | private List packages = new ArrayList(); 34 | private RequirementList requirements; 35 | 36 | public PackageList(RequirementList requirements) { 37 | this.requirements = requirements; 38 | setRequirements(requirements); 39 | } 40 | 41 | private void setRequirements(RequirementList requirements) { 42 | 43 | packages.clear(); 44 | for (Requirement r: requirements) { 45 | if (r instanceof Package) { 46 | packages.add((Package) r); 47 | } 48 | } 49 | 50 | this.requirements = requirements; 51 | this.requirements.addChangeListener(this); 52 | notifyChangeListeners(); 53 | } 54 | 55 | public void sort() { 56 | java.util.Collections.sort(packages); 57 | } 58 | 59 | public Package get(int i) { 60 | return packages.get(i); 61 | } 62 | 63 | public Package get(String id) { 64 | Requirement r = requirements.get(id); 65 | if (!(r instanceof Package)) { 66 | return null; 67 | } 68 | return (Package) r; 69 | } 70 | 71 | 72 | public Iterator iterator() { 73 | return packages.iterator(); 74 | } 75 | 76 | public int size() { 77 | return packages.size(); 78 | } 79 | 80 | public int indexOf(Package r) { 81 | return packages.indexOf(r); 82 | } 83 | 84 | 85 | /** 86 | * easily have change listeners 87 | */ 88 | private ChangeListenerList listeners = new ChangeListenerList(); 89 | 90 | public void addChangeListener(ChangeListener l) { 91 | listeners.addChangeListener(l); 92 | } 93 | public void removeChangeListener(ChangeListener l) { 94 | listeners.removeChangeListener(l); 95 | } 96 | private void notifyChangeListeners() { 97 | listeners.notifyChangeListeners(this); 98 | } 99 | 100 | public void stateChanged(ChangeEvent e) { 101 | if (!(e.getSource() instanceof RequirementList)) { 102 | throw new RuntimeException("getting change events with source not a RequirementList"); 103 | } 104 | 105 | setRequirements((RequirementList) e.getSource()); 106 | } 107 | 108 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/PackageListSelectionHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | import java.util.ArrayList; 23 | 24 | import javax.swing.JTable; 25 | import javax.swing.ListSelectionModel; 26 | import javax.swing.event.ListSelectionEvent; 27 | import javax.swing.event.ListSelectionListener; 28 | 29 | import de.haukerehfeld.quakeinjector.packagelist.model.PackageListModel; 30 | 31 | class PackageListSelectionHandler implements ListSelectionListener { 32 | private PackageListModel list; 33 | 34 | private JTable table; 35 | 36 | private ArrayList listeners = new ArrayList(); 37 | 38 | private int lastSelection = -1; 39 | 40 | public PackageListSelectionHandler(PackageListModel list, 41 | JTable table) { 42 | this.list = list; 43 | this.table = table; 44 | } 45 | 46 | /** 47 | * Listen on selection changes 48 | */ 49 | @Override 50 | public void valueChanged(ListSelectionEvent e) { 51 | ListSelectionModel lsm = (ListSelectionModel) e.getSource(); 52 | 53 | if (!lsm.isSelectionEmpty()) { 54 | int selection = table.convertRowIndexToModel(getSelection(lsm)); 55 | if (lastSelection == selection) { 56 | return; 57 | } 58 | notifySelectionListeners(list.getPackage(selection)); 59 | lastSelection = selection; 60 | } 61 | } 62 | 63 | /** 64 | * Find out what entry was selected 65 | */ 66 | public int getSelection(ListSelectionModel lsm) { 67 | int selection = -1; 68 | // Find out which indexes are selected. 69 | int minIndex = lsm.getMinSelectionIndex(); 70 | int maxIndex = lsm.getMaxSelectionIndex(); 71 | for (int i = minIndex; i <= maxIndex; i++) { 72 | if (lsm.isSelectedIndex(i)) { 73 | selection = i; 74 | break; 75 | } 76 | } 77 | return selection; 78 | } 79 | 80 | public void addSelectionListener(SelectionListener l) { 81 | listeners.add(l); 82 | } 83 | 84 | private void notifySelectionListeners(Package selection) { 85 | for (SelectionListener l: listeners) { 86 | l.selectionChanged(selection); 87 | } 88 | } 89 | 90 | public interface SelectionListener { 91 | public void selectionChanged(Package selection); 92 | } 93 | } 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/PackageOverwriteDialog.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | import java.awt.BorderLayout; 23 | import java.awt.Dimension; 24 | import java.awt.GridBagConstraints; 25 | import java.awt.GridBagLayout; 26 | import java.awt.event.ActionEvent; 27 | import java.awt.event.ActionListener; 28 | import java.util.ArrayList; 29 | import java.util.HashMap; 30 | import java.util.List; 31 | import java.util.Map; 32 | 33 | import javax.swing.BorderFactory; 34 | import javax.swing.Box; 35 | import javax.swing.JButton; 36 | import javax.swing.JCheckBox; 37 | import javax.swing.JDialog; 38 | import javax.swing.JFrame; 39 | import javax.swing.JLabel; 40 | import javax.swing.JPanel; 41 | import javax.swing.JScrollPane; 42 | import javax.swing.border.Border; 43 | 44 | import de.haukerehfeld.quakeinjector.gui.OkayCancelApplyPanel; 45 | import de.haukerehfeld.quakeinjector.gui.ScrollablePanel; 46 | 47 | public class PackageOverwriteDialog extends JDialog { 48 | private final static String windowTitle = "Overwrite Package Files?"; 49 | 50 | private final static int BUTTONMARGIN = 6; 51 | 52 | private final static int MARGINWIDTH = 5; 53 | 54 | private final static int DESCRIPTIONMARGINWIDTH = 5 + MARGINWIDTH; 55 | private final static int DESCRIPTIONMARGINHEIGHT = 10; 56 | private final static Border DESCRIPTIONMARGIN 57 | = BorderFactory.createEmptyBorder(DESCRIPTIONMARGINHEIGHT, 58 | DESCRIPTIONMARGINWIDTH, 59 | DESCRIPTIONMARGINHEIGHT, 60 | DESCRIPTIONMARGINWIDTH); 61 | 62 | private final static int LINEENTRYMARGINWIDTH = 4 + MARGINWIDTH; 63 | private final static int LINEENTRYMARGINHEIGHT = 2; 64 | private final static Border LINEENTRYMARGIN 65 | = BorderFactory.createEmptyBorder(LINEENTRYMARGINHEIGHT, 66 | LINEENTRYMARGINWIDTH, 67 | LINEENTRYMARGINHEIGHT, 68 | LINEENTRYMARGINWIDTH); 69 | 70 | private final static int CHECKBOXINDENT = 17; 71 | private final static int CHECKBOXEXTRAMARGIN = 0; 72 | private final static Border NOCHECKBOXLINEENTRYMARGIN 73 | = BorderFactory.createEmptyBorder(LINEENTRYMARGINHEIGHT + CHECKBOXEXTRAMARGIN, 74 | LINEENTRYMARGINWIDTH + CHECKBOXINDENT, 75 | LINEENTRYMARGINHEIGHT + CHECKBOXEXTRAMARGIN, 76 | LINEENTRYMARGINWIDTH); 77 | 78 | private final static int SECTIONMARGIN = 15; 79 | 80 | private final JFrame parent; 81 | 82 | private final JPanel panel; 83 | 84 | private boolean canceled = true; 85 | 86 | private ArrayList overwriteList = new ArrayList(); 87 | private ArrayList alwaysWriteList = new ArrayList(); 88 | 89 | private final Map overwriteBoxes = new HashMap(); 90 | 91 | public PackageOverwriteDialog(final JFrame frame) { 92 | super(frame, windowTitle, true); 93 | this.parent = frame; 94 | 95 | panel = new ScrollablePanel(50, 50); 96 | panel.setLayout(new GridBagLayout()); 97 | panel.setOpaque(false); 98 | 99 | JScrollPane panelScroll = new JScrollPane(panel); 100 | panelScroll.getViewport().setBackground(javax.swing.UIManager.getColor("TextPane.background")); 101 | add(panelScroll, BorderLayout.CENTER); 102 | 103 | final JButton okay = new JButton("Write files"); 104 | final JButton cancel = new JButton("Cancel"); 105 | 106 | okay.addActionListener(new ActionListener() { 107 | public void actionPerformed(ActionEvent e) { 108 | canceled = false; 109 | 110 | setVisible(false); 111 | dispose(); 112 | } 113 | }); 114 | cancel.addActionListener(new ActionListener() { 115 | public void actionPerformed(ActionEvent e) { 116 | setVisible(false); 117 | dispose(); 118 | } 119 | }); 120 | 121 | { 122 | 123 | 124 | add(new OkayCancelApplyPanel(okay, cancel, null, false), BorderLayout.PAGE_END); 125 | } 126 | 127 | } 128 | 129 | private void addDescriptionLabel(final String text, final int line) { 130 | JLabel description = new JLabel(text); 131 | description.setLabelFor(this); 132 | description.setOpaque(true); 133 | description.setBorder(DESCRIPTIONMARGIN); 134 | panel.add(description, new GridBagConstraints() {{ 135 | gridy = line; 136 | 137 | gridwidth = 2; 138 | fill = BOTH; 139 | anchor = LINE_START; 140 | }}); 141 | } 142 | 143 | private void addDescription() { 144 | addDescriptionLabel(windowTitle, 0); 145 | } 146 | 147 | private void addAlwaysWriteDescription(final int line) { 148 | addDescriptionLabel("Files that need to be installed:", line); 149 | } 150 | 151 | 152 | public void addFile(String name, boolean overwrite) { 153 | List list = (overwrite ? overwriteList : alwaysWriteList); 154 | list.add(name); 155 | } 156 | 157 | public List getOverwritten() { 158 | List overwritten = new ArrayList(alwaysWriteList); 159 | for (Map.Entry e: overwriteBoxes.entrySet()) { 160 | String name = e.getKey(); 161 | JCheckBox box = e.getValue(); 162 | if (box.isSelected()) { 163 | overwritten.add(name); 164 | } 165 | } 166 | 167 | return overwritten; 168 | } 169 | 170 | public boolean isCanceled() { 171 | return canceled; 172 | } 173 | 174 | /** 175 | * Only call once 176 | */ 177 | public void packAndShow() { 178 | addDescription(); 179 | int lines = 1; 180 | 181 | java.util.Collections.sort(overwriteList); 182 | for (String name: overwriteList) { 183 | JCheckBox overwrite = new JCheckBox(name); 184 | overwrite.setSelected(true); 185 | overwrite.setOpaque(false); 186 | overwrite.setBorder(LINEENTRYMARGIN); 187 | final int finalLines = lines; 188 | panel.add(overwrite, new OverwriteBoxConstraints() {{ 189 | gridy = finalLines; 190 | }}); 191 | overwriteBoxes.put(name, overwrite); 192 | ++lines; 193 | } 194 | 195 | if (!alwaysWriteList.isEmpty()) { 196 | { 197 | final int finalLines = lines++; 198 | panel.add(Box.createRigidArea(new Dimension(0,SECTIONMARGIN)), 199 | new GridBagConstraints() {{ 200 | gridy = finalLines; 201 | }}); 202 | } 203 | java.util.Collections.sort(alwaysWriteList); 204 | addAlwaysWriteDescription(lines++); 205 | 206 | for (String name: alwaysWriteList) { 207 | JLabel listEntry = new JLabel(name); 208 | listEntry.setOpaque(false); 209 | listEntry.setBorder(NOCHECKBOXLINEENTRYMARGIN); 210 | final int finalLines = lines; 211 | panel.add(listEntry, new OverwriteBoxConstraints() {{ 212 | gridy = finalLines; 213 | }}); 214 | ++lines; 215 | } 216 | } 217 | pack(); 218 | setLocationRelativeTo(parent); 219 | setVisible(true); 220 | } 221 | 222 | class OverwriteBoxConstraints extends GridBagConstraints {{ 223 | anchor = LINE_START; 224 | fill = HORIZONTAL; 225 | weightx = 1; 226 | }}; 227 | 228 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/PackageTable.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | import java.awt.Dimension; 23 | import java.awt.Color; 24 | import java.awt.Component; 25 | 26 | import javax.swing.UIManager; 27 | 28 | import javax.swing.JTable; 29 | import javax.swing.JComponent; 30 | import javax.swing.border.EmptyBorder; 31 | import javax.swing.table.TableCellRenderer; 32 | import javax.swing.table.TableRowSorter; 33 | 34 | import de.haukerehfeld.quakeinjector.packagelist.model.PackageListModel; 35 | 36 | /** 37 | * @todo check if dependency on de.haukerehfeld.quakeinjector.packagelist.model.PackageListModel is necessary 38 | */ 39 | public class PackageTable extends JTable { 40 | private static final int ALTERNATING = 10; 41 | private static final Color NORMALROWCOLOR = UIManager.getColor("Table.background"); 42 | private static final Color ALTERNATINGROWCOLOR = createAlternatingColor(NORMALROWCOLOR, ALTERNATING); 43 | private static final int CELLPADDING = 2; 44 | 45 | private final EmptyBorder border = new EmptyBorder(0, CELLPADDING, 0, CELLPADDING); 46 | 47 | 48 | public PackageTable(PackageListModel maplist) { 49 | super(maplist); 50 | 51 | final TableRowSorter sorter = new TableRowSorter(maplist); 52 | setRowSorter(sorter); 53 | 54 | setPreferredScrollableViewportSize(new Dimension(500, 500)); 55 | setFillsViewportHeight(true); 56 | setColumnSelectionAllowed(false); 57 | //setCellSelectionEnabled(false); 58 | setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); 59 | setShowGrid(false); 60 | setIntercellSpacing(new Dimension(0, 0)); 61 | setRowHeight(getFontMetrics(getFont()).getHeight()); 62 | 63 | setDefaultRenderer(Package.Rating.class, new PackageListModel.RatingRenderer()); 64 | } 65 | 66 | /** 67 | * Shades alternate rows in different colors. 68 | */ 69 | public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { 70 | Component c = super.prepareRenderer(renderer, row, column); 71 | if (isCellSelected(row, column) == false) { 72 | c.setBackground(colorForRow(row)); 73 | c.setForeground(UIManager.getColor("Table.foreground")); 74 | } else { 75 | c.setBackground(UIManager.getColor("Table.selectionBackground")); 76 | c.setForeground(UIManager.getColor("Table.selectionForeground")); 77 | } 78 | //disable cell focus 79 | if (c instanceof JComponent) { 80 | if (!(c instanceof PackageListModel.RatingRenderer)) { 81 | ((JComponent) c).setBorder(border); 82 | } 83 | else { 84 | ((JComponent) c).setBorder(new EmptyBorder(0, 0, 0, 0)); 85 | } 86 | } 87 | return c; 88 | } 89 | 90 | /** 91 | * Returns the appropriate background color for the given row. 92 | */ 93 | protected Color colorForRow(int row) { 94 | return (row % 2 == 0) ? NORMALROWCOLOR : ALTERNATINGROWCOLOR; 95 | } 96 | 97 | @Override 98 | @SuppressWarnings("unchecked") 99 | public TableRowSorter getRowSorter() { 100 | return (TableRowSorter) super.getRowSorter(); 101 | } 102 | 103 | /** 104 | * Calculate a darker or brighter version of a certain color 105 | */ 106 | public static Color createAlternatingColor(Color c, int amount) { 107 | int r = c.getRed(); 108 | int g = c.getGreen(); 109 | int b = c.getBlue(); 110 | 111 | 112 | return new Color(r > 127 ? r - amount : r + amount, 113 | g > 127 ? g - amount : g + amount, 114 | b > 127 ? b - amount : b + amount); 115 | } 116 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/PercentageProgressListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | 21 | package de.haukerehfeld.quakeinjector; 22 | 23 | /** 24 | * Report Percentages instaedd of absolute values 25 | */ 26 | public class PercentageProgressListener implements ProgressListener { 27 | private final long size; 28 | private final ProgressListener progress; 29 | 30 | public PercentageProgressListener(long size, ProgressListener progress) { 31 | // if (size <= 0) { 32 | // throw new IllegalArgumentException("Size must be > 0: was " + size); 33 | // } 34 | 35 | this.progress = progress; 36 | this.size = size; 37 | } 38 | 39 | /** 40 | * tell the reporter of the size of uncompression 41 | */ 42 | public void publish(long writtenBytes) { 43 | long per; 44 | if (size == 0) { 45 | per = 0; 46 | } 47 | else { 48 | per = Math.round(100 * writtenBytes / (double) size); 49 | } 50 | progress.publish(per); 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/ProgressListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | 21 | package de.haukerehfeld.quakeinjector; 22 | 23 | public interface ProgressListener { 24 | /** 25 | * tell the reporter how much got written 26 | */ 27 | public void publish(long writtenBytes); 28 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/ProgressListenerInputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | 21 | package de.haukerehfeld.quakeinjector; 22 | 23 | import java.io.IOException; 24 | import java.io.InputStream; 25 | 26 | /** 27 | * report progress while reading InputStream 28 | * 29 | * Note that skipping doesn't add to the progress, and marking isn't supported 30 | */ 31 | public class ProgressListenerInputStream extends InputStream { 32 | private final InputStream in; 33 | private final ProgressListener progress; 34 | 35 | public ProgressListenerInputStream(InputStream in, ProgressListener progress) { 36 | this.in = in; 37 | this.progress = progress; 38 | } 39 | 40 | @Override 41 | public int read() throws IOException { 42 | progress.publish(1); 43 | return in.read(); 44 | } 45 | 46 | @Override 47 | public int read(byte[] b) throws IOException { 48 | return read(b, 0, b.length); 49 | } 50 | 51 | @Override 52 | public int read(byte[] b, int off, int len) throws IOException { 53 | int readcount = in.read(b, off, len); 54 | 55 | if (readcount > 0) { 56 | progress.publish(readcount); 57 | } 58 | return readcount; 59 | } 60 | 61 | @Override 62 | public long skip(long n) throws IOException { 63 | return in.skip(n); 64 | } 65 | 66 | @Override 67 | public int available() throws IOException { 68 | return in.available(); 69 | } 70 | 71 | @Override 72 | public void mark(int readlimit) { 73 | 74 | } 75 | 76 | @Override 77 | public void reset() throws IOException { 78 | in.reset(); 79 | } 80 | 81 | @Override 82 | public boolean markSupported() { 83 | return false; 84 | } 85 | } 86 | 87 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/RelativePath.java: -------------------------------------------------------------------------------- 1 | package de.haukerehfeld.quakeinjector; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * this class provides functions used to generate a relative path 10 | * from two absolute paths 11 | * @see http://www.devx.com/tips/Tip/13737 12 | */ 13 | public class RelativePath { 14 | /** 15 | * break a path down into individual elements and add to a list. 16 | * example : if a path is /a/b/c/d.txt, the breakdown will be [d.txt,c,b,a] 17 | * @param f input file 18 | * @return a List collection with the individual elements of the path in 19 | reverse order 20 | */ 21 | private static List getPathList(File f) { 22 | List list = new ArrayList(); 23 | File parent; 24 | try { 25 | parent = f.getCanonicalFile(); 26 | while (parent != null) { 27 | list.add(parent); 28 | parent = parent.getParentFile(); 29 | } 30 | } 31 | catch (IOException e) { 32 | e.printStackTrace(); 33 | list = null; 34 | } 35 | return list; 36 | } 37 | 38 | private static int getFirstRootPosition(List file) { 39 | File[] roots = File.listRoots(); 40 | for (File root: roots) { 41 | // System.out.println("Root: " + root); 42 | } 43 | int i = 0; 44 | done: while (i < file.size() - 1) { 45 | for (File root: roots) { 46 | if (file.get(i).equals(root)) { 47 | break done; 48 | } 49 | // System.out.println(root + " isn't " + file.get(i)); 50 | } 51 | i++; 52 | } 53 | // System.out.println("Root is " + file.get(i)); 54 | return i; 55 | } 56 | /** 57 | * figure out a string representing the relative path of 58 | * 'f' with respect to 'r' 59 | * @param home home path list 60 | * @param file path of file list 61 | */ 62 | private static File matchPathLists(List home, List file) { 63 | if (file.size() <= 0) { 64 | return new File(""); 65 | } 66 | if (home.size() <= 0) { 67 | return file.get(0); 68 | } 69 | 70 | // start at the beginning of the lists 71 | // iterate while both lists are equal 72 | final int homeLast = home.size() - 1; 73 | final int fileLast = file.size() - 1; 74 | 75 | 76 | 77 | 78 | int i = getFirstRootPosition(home); 79 | int j = getFirstRootPosition(file); 80 | 81 | //the root isn't identical (common on win32) - just return an absolute path 82 | if (!home.get(i).equals(file.get(j))) { 83 | // System.out.println(home); 84 | // System.out.println(file); 85 | // System.out.println(home.get(i) + " != " + file.get(j)); 86 | return file.get(0); 87 | } 88 | 89 | // first eliminate common root 90 | while((i >= 0) && (j >= 0) && (home.get(i).equals(file.get(j)))) { 91 | i--; 92 | j--; 93 | } 94 | 95 | StringBuilder result = new StringBuilder(); 96 | // for each remaining level in the home path, add a .. 97 | for(; i >= 0; i--) { 98 | result.append(".." + File.separator); 99 | } 100 | 101 | // for each level in the file path, add the path 102 | for(; j >= 1; j--) { 103 | result.append(file.get(j).getName() + File.separator); 104 | } 105 | 106 | if (j >= 0) { 107 | // file name doesn't need separator at the end 108 | result.append(file.get(j).getName()); 109 | } 110 | return new File(result.toString()); 111 | } 112 | 113 | /** 114 | * get relative path of File 'f' with respect to 'home' directory 115 | * example : home = /a/b/c 116 | * f = /a/d/e/x.txt 117 | * s = getRelativePath(home,f) = ../../d/e/x.txt 118 | * @param home base path, should be a directory, not a file, or it doesn't 119 | make sense 120 | * @param file file to generate path for 121 | * @return path from home to f as a string 122 | */ 123 | public static File getRelativePath(File home, File file){ 124 | List homelist; 125 | List filelist; 126 | 127 | //System.out.println("home: " + home); 128 | //System.out.println("file: " + file); 129 | homelist = getPathList(home); 130 | filelist = getPathList(file); 131 | 132 | File result = matchPathLists(homelist,filelist); 133 | //System.out.println("getName(): " + result.getName()); 134 | //System.out.println("getToString(): " + result.toString()); 135 | 136 | return result; 137 | } 138 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/Requirement.java: -------------------------------------------------------------------------------- 1 | package de.haukerehfeld.quakeinjector; 2 | 3 | import javax.swing.event.ChangeListener; 4 | 5 | /** 6 | * Base class for everything that might ever be referenced as a package. 7 | */ 8 | public interface Requirement extends java.lang.Comparable { 9 | public void addChangeListener(ChangeListener l); 10 | public void removeChangeListener(ChangeListener l); 11 | 12 | /** 13 | * The name of the Requirement 14 | */ 15 | public String getId(); 16 | public boolean isInstalled(); 17 | public void setInstalled(boolean installed); 18 | 19 | /** 20 | * A list of files this requirement installed 21 | */ 22 | public PackageFileList getFileList(); 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/RequirementList.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | import java.util.Collections; 23 | import java.util.HashMap; 24 | import java.util.Iterator; 25 | import java.util.List; 26 | 27 | import javax.swing.event.ChangeListener; 28 | 29 | public class RequirementList implements Iterable { 30 | 31 | private HashMap names = new HashMap(); 32 | private List requirements; 33 | 34 | public RequirementList() { 35 | this.requirements = Collections.emptyList(); 36 | } 37 | public RequirementList(List requirements) { 38 | setRequirements(requirements); 39 | } 40 | 41 | public void setRequirements(List requirements) { 42 | names.clear(); 43 | for (int i = 0; i < requirements.size(); ++i) { 44 | names.put(requirements.get(i).getId(), i); 45 | } 46 | this.requirements = requirements; 47 | 48 | } 49 | 50 | public void setInstalled(PackageFileList l) { 51 | Requirement r = get(l.getId()); 52 | if (r instanceof UnavailableRequirement) { 53 | System.err.println("Can't set installed on UnavailableRequirement " + l.getId()); 54 | return; 55 | } 56 | r.setInstalled(true); 57 | if (r instanceof Package) { 58 | ((Package) r).setFileList(l); 59 | } 60 | } 61 | 62 | public Requirement get(int i) { 63 | return requirements.get(i); 64 | } 65 | 66 | public Requirement get(String id) { 67 | Integer i = names.get(id); 68 | if (i == null) { 69 | return new UnavailableRequirement(id); 70 | } 71 | return requirements.get(i); 72 | } 73 | 74 | public Iterator iterator() { 75 | return requirements.iterator(); 76 | } 77 | 78 | public int size() { 79 | return requirements.size(); 80 | } 81 | 82 | public int indexOf(Requirement r) { 83 | return requirements.indexOf(r); 84 | } 85 | 86 | 87 | /** 88 | * easily have change listeners 89 | */ 90 | private ChangeListenerList listeners = new ChangeListenerList(); 91 | 92 | public void addChangeListener(ChangeListener l) { 93 | listeners.addChangeListener(l); 94 | } 95 | public void removeChangeListener(ChangeListener l) { 96 | listeners.removeChangeListener(l); 97 | } 98 | public void notifyChangeListeners() { 99 | listeners.notifyChangeListeners(this); 100 | } 101 | 102 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/RuntimeExecutionException.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | /** 23 | * Like java.util.concurrent.ExecutionException , but at runtime 24 | * 25 | */ 26 | public class RuntimeExecutionException extends RuntimeException { 27 | public RuntimeExecutionException(Throwable cause) { 28 | super(cause); 29 | } 30 | 31 | public RuntimeExecutionException(String msg, Throwable cause) { 32 | super(msg, cause); 33 | } 34 | 35 | 36 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/SaveInstalled.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | import java.io.BufferedInputStream; 23 | import java.io.BufferedOutputStream; 24 | import java.io.File; 25 | import java.io.FileInputStream; 26 | import java.io.FileOutputStream; 27 | import java.io.InputStream; 28 | import java.io.OutputStream; 29 | import java.util.List; 30 | import java.util.Map; 31 | 32 | 33 | public class SaveInstalled { 34 | private final File file; 35 | 36 | public SaveInstalled(File file) { 37 | this.file = file; 38 | } 39 | 40 | private OutputStream getOutputStream() throws java.io.IOException { 41 | if (!file.exists()) { 42 | file.createNewFile(); 43 | } 44 | return new BufferedOutputStream(new FileOutputStream(file)); 45 | } 46 | 47 | private InputStream getInputStream() throws java.io.IOException { 48 | return new BufferedInputStream(new FileInputStream(file)); 49 | } 50 | 51 | 52 | public void write(Iterable list) throws java.io.IOException { 53 | OutputStream out = getOutputStream(); 54 | new InstalledPackageList().write(out, list); 55 | out.close(); 56 | } 57 | 58 | public void write(Map> files) throws java.io.IOException { 59 | OutputStream out = getOutputStream(); 60 | new InstalledPackageList().write(out, files); 61 | out.close(); 62 | } 63 | 64 | public List read() throws java.io.IOException { 65 | InputStream in = getInputStream(); 66 | List result = new InstalledPackageList().read(in); 67 | in.close(); 68 | return result; 69 | } 70 | 71 | } 72 | 73 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/SortableRequirement.java: -------------------------------------------------------------------------------- 1 | package de.haukerehfeld.quakeinjector; 2 | 3 | /** 4 | * Requirements that can be sorted alphabetically by id 5 | */ 6 | public abstract class SortableRequirement implements Requirement { 7 | private final String id; 8 | private boolean isInstalled = false; 9 | 10 | 11 | public SortableRequirement(String id) { 12 | this.id = id; 13 | } 14 | 15 | /** 16 | * get id 17 | */ 18 | public String getId() { return id; } 19 | 20 | public boolean isInstalled() { 21 | return isInstalled; 22 | } 23 | 24 | public void setInstalled(boolean installed) { 25 | isInstalled = installed; 26 | 27 | notifyChangeListeners(); 28 | } 29 | 30 | protected abstract void notifyChangeListeners(); 31 | 32 | public int compareTo(Requirement o) { 33 | return getId().compareTo(((Requirement) o).getId()); 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return getId(); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/SumProgressListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | 21 | package de.haukerehfeld.quakeinjector; 22 | 23 | /** 24 | * Accumulate progress and report it 25 | */ 26 | public class SumProgressListener implements ProgressListener { 27 | private final ProgressListener progress; 28 | private long currentSize = 0; 29 | 30 | public SumProgressListener(ProgressListener progress) { 31 | this.progress = progress; 32 | } 33 | 34 | /** 35 | * tell the reporter of the size of uncompression 36 | */ 37 | public void publish(long writtenBytes) { 38 | currentSize += writtenBytes; 39 | progress.publish(currentSize); 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/ThreadedGetter.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | 23 | 24 | /** 25 | * Get something that a different thread creates. Will block until available. 26 | */ 27 | public interface ThreadedGetter { 28 | public T get() 29 | throws java.util.concurrent.ExecutionException, 30 | java.lang.InterruptedException; 31 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/UnavailableRequirement.java: -------------------------------------------------------------------------------- 1 | package de.haukerehfeld.quakeinjector; 2 | 3 | import javax.swing.event.ChangeListener; 4 | 5 | /** 6 | * A requirement that doesn't have a package available and thus can't be installed (like the quake mission packs) 7 | * 8 | * Immutable. 9 | */ 10 | public class UnavailableRequirement extends SortableRequirement implements Requirement { 11 | public UnavailableRequirement(String id) { 12 | super(id); 13 | } 14 | @Override 15 | public void addChangeListener(ChangeListener l) {} 16 | @Override 17 | protected void notifyChangeListeners() {} 18 | 19 | public PackageFileList getFileList() { 20 | return new PackageFileList(getId()); 21 | } 22 | @Override 23 | public void removeChangeListener(ChangeListener l) {} 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/UninstallWorker.java: -------------------------------------------------------------------------------- 1 | package de.haukerehfeld.quakeinjector; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.BufferedInputStream; 6 | import java.util.Iterator; 7 | 8 | import javax.swing.SwingWorker; 9 | 10 | 11 | /** 12 | * Install maps in a worker thread 13 | * Init once and let swing start it - don't reuse 14 | */ 15 | public class UninstallWorker extends SwingWorker { 16 | private String baseDirectory; 17 | private PackageFileList files; 18 | 19 | public UninstallWorker(PackageFileList files, String baseDirectory) { 20 | this.files = files; 21 | this.baseDirectory = baseDirectory; 22 | } 23 | 24 | @Override 25 | public Void doInBackground() { 26 | uninstall(files); 27 | return null; 28 | } 29 | 30 | public void uninstall(PackageFileList files) { 31 | //we rely on the descending order of paths in the file list here! 32 | //otherwise, dirs wouldn't get deleted last 33 | final int fileCount = files.size(); 34 | int i = 1; 35 | Iterator it = files.descendingIterator(); 36 | while (it.hasNext()) { 37 | FileInfo file = it.next(); 38 | 39 | 40 | File f = new File(baseDirectory + File.separator + file.getName()); 41 | 42 | long supposedCrc = file.getChecksum(); 43 | String skipMsg = "Couldn't check CRC for " + f + ", deleting..."; 44 | if (supposedCrc != 0) { 45 | try { 46 | BufferedInputStream in = new BufferedInputStream(new FileInputStream(f)); 47 | long crc = Utils.getCrc32(in, null); 48 | in.close(); 49 | if (crc != supposedCrc) { 50 | System.err.println("CRC for " + f + " didn't match, not deleting"); 51 | continue; 52 | } 53 | } 54 | catch (java.io.IOException e) { 55 | System.err.println(skipMsg); 56 | } 57 | } 58 | else { 59 | System.err.println(skipMsg); 60 | } 61 | 62 | if (!f.delete()) { 63 | System.out.println("Couldn't delete " + f); 64 | } 65 | else { 66 | System.out.println("Deleted file " + f); 67 | } 68 | int progress = i * 100 / fileCount; 69 | setProgress(progress); 70 | i++; 71 | } 72 | } 73 | 74 | @Override 75 | public void done() { 76 | } 77 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/UpdateRequirementListWorker.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | //import java.awt.*; 23 | import java.util.List; 24 | 25 | import javax.swing.SwingWorker; 26 | 27 | public class UpdateRequirementListWorker extends SwingWorker { 28 | private final RequirementList requirementList; 29 | private final ThreadedGetter> requirementGetter; 30 | private final ThreadedGetter> installedGetter; 31 | 32 | private List requirement; 33 | private List installed; 34 | 35 | 36 | public UpdateRequirementListWorker(RequirementList requirementList, 37 | ThreadedGetter> requirements, 38 | ThreadedGetter> installed) { 39 | this.requirementList = requirementList; 40 | this.requirementGetter = requirements; 41 | this.installedGetter = installed; 42 | } 43 | 44 | @Override 45 | public Void doInBackground() { 46 | try { 47 | 48 | requirement = requirementGetter.get(); 49 | installed = installedGetter.get(); 50 | } 51 | catch (java.util.concurrent.ExecutionException e) { 52 | System.err.println(e.getMessage()); 53 | e.printStackTrace(); 54 | } 55 | catch (java.lang.InterruptedException e) { 56 | System.err.println("Couldn't wait for results!" + e.getMessage()); 57 | e.printStackTrace(); 58 | 59 | } 60 | return null; 61 | } 62 | 63 | 64 | @Override 65 | public void done() { 66 | synchronized (requirementList) { 67 | requirementList.setRequirements(requirement); 68 | 69 | for (PackageFileList l: installed) { 70 | requirementList.setInstalled(l); 71 | } 72 | 73 | requirementList.notifyChangeListeners(); 74 | 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | 21 | package de.haukerehfeld.quakeinjector; 22 | 23 | 24 | import java.io.BufferedOutputStream; 25 | import java.io.File; 26 | import java.io.FileOutputStream; 27 | import java.io.IOException; 28 | import java.io.InputStream; 29 | import java.io.OutputStream; 30 | import java.util.ArrayList; 31 | import java.util.Iterator; 32 | import java.util.List; 33 | 34 | import java.util.zip.CRC32; 35 | 36 | public class Utils { 37 | public static final int BUFFERSIZE = 4096; 38 | 39 | public static String join(final Iterable objs, final String delimiter) { 40 | Iterator iter = objs.iterator(); 41 | if (!iter.hasNext()) 42 | return ""; 43 | StringBuffer buffer = new StringBuffer(String.valueOf(iter.next())); 44 | while (iter.hasNext()) { 45 | buffer.append(delimiter).append(String.valueOf(iter.next())); 46 | } 47 | return buffer.toString(); 48 | } 49 | 50 | /** 51 | * Like File.mkdirs() this creates all parent directories of f, but returns them in a list 52 | */ 53 | public static List mkdirs(File f) { 54 | ArrayList files = new ArrayList(); 55 | 56 | if (f.isDirectory()) { 57 | files.add(f); 58 | } 59 | 60 | File parentDir = f.getParentFile(); 61 | while (parentDir != null && !parentDir.exists()) { 62 | files.add(parentDir); 63 | parentDir = parentDir.getParentFile(); 64 | } 65 | 66 | java.util.Collections.reverse(files); 67 | 68 | for (File dir: files) { 69 | System.out.println("Creating dir " + dir); 70 | dir.mkdir(); 71 | } 72 | 73 | return files; 74 | } 75 | 76 | /** 77 | * Write stream to file in chunks of 2048 bytes 78 | * 79 | * doesn't do any checks. 80 | */ 81 | public static long writeFile(InputStream in, File file, ProgressListener progress) 82 | throws IOException { 83 | return writeFile(in, file, 2048, progress); 84 | } 85 | 86 | /** 87 | * Write stream to file 88 | * @return crc32 checksum 89 | */ 90 | public static long writeFile(final InputStream in, 91 | final File file, 92 | final int BUFFERSIZE, 93 | final ProgressListener progress) throws IOException { 94 | final BufferedOutputStream dest = new BufferedOutputStream(new FileOutputStream(file), 95 | BUFFERSIZE); 96 | 97 | long crc = copy(in, dest, BUFFERSIZE, progress); 98 | dest.close(); 99 | 100 | return crc; 101 | } 102 | 103 | public static long copy(final InputStream in, 104 | final OutputStream out, 105 | final int BUFFERSIZE, 106 | final ProgressListener progress) throws IOException { 107 | byte data[] = new byte[BUFFERSIZE]; 108 | CRC32 crc = new CRC32(); 109 | 110 | int readcount; 111 | while ((readcount = in.read(data, 0, BUFFERSIZE)) != -1) { 112 | if (progress != null) { 113 | progress.publish(readcount); 114 | } 115 | out.write(data, 0, readcount); 116 | crc.update(data, 0, readcount); 117 | } 118 | out.flush(); 119 | return crc.getValue(); 120 | } 121 | 122 | 123 | public static long getCrc32(final InputStream in, final ProgressListener progress) throws IOException { 124 | return copy(in, new NoOutputStream(), BUFFERSIZE, progress); 125 | } 126 | 127 | public static class NoOutputStream extends OutputStream { 128 | public void write(int b) {} 129 | public void write(byte[] b) {} 130 | public void write(byte[] b, int off, int len) {} 131 | } 132 | 133 | 134 | /** 135 | Check if files can indeed be written inside a certain directory 136 | 137 | Tries to write a temp file in a dir. 138 | @param directory The directory to test 139 | */ 140 | public static boolean canWriteToDirectory(File directory) { 141 | try { 142 | File tmp = File.createTempFile("writeTest", null, directory); 143 | tmp.delete(); 144 | } 145 | catch (IOException e) { 146 | System.out.println(e); 147 | return false; 148 | } 149 | return true; 150 | } 151 | 152 | public static javax.swing.ImageIcon createImageIcon(String path, String description) throws java.io.IOException { 153 | java.net.URL imgURL = Utils.class.getResource(path); 154 | if (imgURL != null) { 155 | return new javax.swing.ImageIcon(imgURL, description); 156 | } else { 157 | throw new java.io.IOException("Couldn't find file: " + path); 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/XmlUtils.java: -------------------------------------------------------------------------------- 1 | package de.haukerehfeld.quakeinjector; 2 | 3 | import java.io.InputStream; 4 | import java.util.Iterator; 5 | 6 | import javax.xml.parsers.DocumentBuilder; 7 | import javax.xml.parsers.DocumentBuilderFactory; 8 | 9 | import org.w3c.dom.Document; 10 | import org.w3c.dom.Element; 11 | import org.w3c.dom.Node; 12 | import org.w3c.dom.NodeList; 13 | 14 | public class XmlUtils { 15 | /** 16 | * get the first element in the parent element that's named name or null if no such element exists 17 | */ 18 | public static Element getFirstElement(Element parent, String name) { 19 | NodeList list = parent.getElementsByTagName(name); 20 | 21 | Node node = list.item(0); 22 | if (node == null) { 23 | //throw new RuntimeException("Malformed XML: No such node: " + name); 24 | return null; 25 | } 26 | 27 | if (!isElement(node)) { 28 | throw new RuntimeException("XML Parsing error: " + name + " is not an Element"); 29 | } 30 | 31 | return (Element) node; 32 | } 33 | 34 | public static Document getDocument(InputStream xml) 35 | throws java.io.IOException, 36 | org.xml.sax.SAXException { 37 | DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 38 | dbf.setIgnoringElementContentWhitespace(true); 39 | DocumentBuilder db; 40 | try { 41 | db = dbf.newDocumentBuilder(); 42 | } 43 | catch (javax.xml.parsers.ParserConfigurationException e) { 44 | throw new RuntimeException("Parser misconfiguration"); 45 | } 46 | 47 | Document doc = db.parse(xml); 48 | return doc; 49 | } 50 | 51 | 52 | public static java.lang.Iterable iterate(NodeList list) { 53 | return new NodeListIterator(list); 54 | } 55 | 56 | public static class NodeListIterator implements Iterator, 57 | java.lang.Iterable { 58 | private NodeList list; 59 | private int i = 0; 60 | 61 | public NodeListIterator(NodeList list) { 62 | this.list = list; 63 | } 64 | 65 | public Node next() { 66 | i++; 67 | 68 | return list.item(i); 69 | } 70 | 71 | public boolean hasNext() { 72 | return i + 1 < list.getLength(); 73 | } 74 | 75 | public void remove() { 76 | throw new java.lang.UnsupportedOperationException(); 77 | } 78 | 79 | public Iterator iterator() { 80 | return this; 81 | } 82 | } 83 | 84 | public static boolean isElement(Node n) { 85 | return (n.getNodeType() == Node.ELEMENT_NODE); 86 | } 87 | 88 | /** 89 | * Gets the String value of the node. 90 | If the node does not contain text then an empty String is returned 91 | * @param node the node of interest 92 | * @return the value of that node 93 | */ 94 | public static String getTextForNode(Node node) 95 | { 96 | NodeList children = node.getChildNodes(); 97 | if (children == null) { 98 | return ""; 99 | } 100 | 101 | for (int i = 0; i < children.getLength(); i++) { 102 | Node childNode = children.item(i); 103 | if ((childNode.getNodeType() == Node.TEXT_NODE) 104 | || (childNode.getNodeType() == Node.CDATA_SECTION_NODE)) { 105 | return childNode.getNodeValue(); 106 | } 107 | } 108 | 109 | return ""; 110 | } 111 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/ZipInspect.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector; 21 | 22 | import java.io.BufferedOutputStream; 23 | import java.io.BufferedReader; 24 | import java.io.BufferedWriter; 25 | import java.io.Console; 26 | import java.io.File; 27 | import java.io.FileInputStream; 28 | import java.io.FileOutputStream; 29 | import java.io.FileReader; 30 | import java.io.FileWriter; 31 | import java.util.AbstractMap; 32 | import java.util.ArrayList; 33 | import java.util.HashMap; 34 | import java.util.List; 35 | import java.util.Map; 36 | import java.util.SortedMap; 37 | import java.util.TreeMap; 38 | import java.util.zip.ZipEntry; 39 | 40 | /** 41 | * Create an xml of all the files in the zips 42 | */ 43 | public class ZipInspect { 44 | private final static String file = "zipFiles.xml"; 45 | private final static String oldSelectionsFile = "essentialfiles.cfg"; 46 | 47 | public static void main(String[] args) { 48 | Console con = System.console(); 49 | if (con == null) { 50 | System.err.println("Don't start from ant, it doesn't support an interactive console!"); 51 | System.exit(1); 52 | } 53 | 54 | if (args.length < 1) { 55 | System.err.println("First parameter need's to be a valid directory!"); 56 | System.exit(1); 57 | } 58 | String parentDir = args[0]; 59 | 60 | if (!(new File(parentDir).exists())) { 61 | System.err.println("First parameter need's to be a valid directory!"); 62 | System.exit(1); 63 | } 64 | 65 | Configuration config = new Configuration(QuakeInjector.configFile); 66 | 67 | List requirements = null; 68 | { 69 | final PackageDatabaseParserWorker requirementsParser 70 | = new PackageDatabaseParserWorker(config.RepositoryDatabasePath.get()); 71 | requirementsParser.execute(); 72 | 73 | 74 | try { 75 | requirements = requirementsParser.get(); 76 | } 77 | catch (Exception e) { 78 | System.err.println("Couldn't get packages " + e); 79 | e.printStackTrace(); 80 | System.exit(1); 81 | } 82 | } 83 | java.util.Collections.sort(requirements); 84 | 85 | Map> packageFiles = new TreeMap>(); 86 | SortedMap>> duplicateFiles 87 | = new TreeMap>>(); 88 | 89 | for (Requirement r: requirements) { 90 | //only check where package exists 91 | if (!(r instanceof Package)) { 92 | continue; 93 | } 94 | 95 | Package p = (Package) r; 96 | p.setInstalled(true); 97 | 98 | File f = new File(parentDir + File.separator + r.getId() + ".zip"); 99 | if (!f.exists()) { 100 | System.out.println("WARNING: " + f + " doesn't exist!"); 101 | continue; 102 | } 103 | System.out.println(f); 104 | 105 | try { 106 | FileInputStream in = new FileInputStream(f); 107 | InspectZipWorker inspector = new InspectZipWorker(in); 108 | inspector.execute(); 109 | 110 | final List entries = inspector.get(); 111 | 112 | final PackageFileList zipFiles = new PackageFileList(p.getId()); 113 | String dir = p.getRelativeBaseDir(); 114 | 115 | for (ZipEntry e: entries) { 116 | String file = ""; 117 | if (dir != null) { 118 | file = dir; 119 | } 120 | file += e.getName(); 121 | 122 | FileInfo info = new FileInfo(file, e.getCrc()); 123 | zipFiles.add(info); 124 | 125 | List> dupMaps = 126 | duplicateFiles.get(file.toLowerCase()); 127 | if (dupMaps == null) { 128 | dupMaps = new ArrayList>(); 129 | duplicateFiles.put(file.toLowerCase(), dupMaps); 130 | } 131 | dupMaps.add(new AbstractMap.SimpleEntry(p, info)); 132 | 133 | } 134 | p.setFileList(zipFiles); 135 | packageFiles.put(p, zipFiles); 136 | } 137 | catch (Exception e) { 138 | e.printStackTrace(); 139 | } 140 | } 141 | 142 | Map oldSelections = new HashMap(); 143 | if (new File(oldSelectionsFile).exists()) { 144 | //load old selections 145 | try { 146 | BufferedReader in = new BufferedReader(new FileReader(oldSelectionsFile)); 147 | String line; 148 | while ((line = in.readLine()) != null) { 149 | String[] l = line.split(","); 150 | oldSelections.put(l[0], Boolean.parseBoolean(l[1])); 151 | } 152 | in.close(); 153 | } catch (java.io.IOException e) { 154 | System.err.println("Couldn't read old selections file"); 155 | } 156 | } 157 | 158 | StringBuilder selectionsFile = new StringBuilder(); 159 | 160 | for (String file: duplicateFiles.keySet()) { 161 | 162 | List> dups = duplicateFiles.get(file); 163 | int count = dups.size(); 164 | if (count > 1) { 165 | List packages = new ArrayList(); 166 | boolean crcDiffers = false; 167 | long crc = -1; 168 | for (Map.Entry e: dups) { 169 | if (crc != -1 && crc != e.getValue().getChecksum()) { 170 | crcDiffers = true; 171 | } 172 | crc = e.getValue().getChecksum(); 173 | 174 | packages.add(e.getKey().getId() + " (crc: " + crc + ")"); 175 | } 176 | 177 | boolean essential = true; 178 | if (!crcDiffers && crc == 0) { 179 | System.out.println(file 180 | + " has duplicates, but all with crc == 0," 181 | + " setting to inessential"); 182 | essential = false; 183 | } 184 | else if (oldSelections.get(file) != null) { 185 | essential = oldSelections.get(file); 186 | System.out.println(file 187 | + " has duplicates, but was previously declared:" 188 | + " essential = " + essential); 189 | } 190 | else { 191 | System.out.println(file 192 | + " has " 193 | + (crcDiffers ? " differing CRC " : " equal ") 194 | + "duplicates in " 195 | + Utils.join(packages, ", ")); 196 | 197 | System.out.println("Is this an essential file? (No: n + RETURN, Yes: RETURN)"); 198 | String yes = con.readLine(); 199 | if (yes != null && yes.equals("n")) { 200 | essential = false; 201 | } 202 | } 203 | 204 | for (Map.Entry e: dups) { 205 | e.getValue().setEssential(essential); 206 | } 207 | 208 | selectionsFile.append(file + "," + essential + "\n"); 209 | } 210 | } 211 | 212 | //write old selections 213 | try { 214 | BufferedWriter out = new BufferedWriter(new FileWriter(oldSelectionsFile)); 215 | out.write(selectionsFile.toString()); 216 | out.close(); 217 | } 218 | catch (java.io.IOException e) { 219 | System.err.println("Couldn't write selections to outfile! " + e); 220 | } 221 | 222 | try { 223 | new InstalledPackageList().write(new BufferedOutputStream(new FileOutputStream(file)), 224 | packageFiles.keySet()); 225 | } 226 | catch (Exception e) { 227 | e.printStackTrace(); 228 | } 229 | } 230 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/database/InstalledMapsParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | 21 | package de.haukerehfeld.quakeinjector.database; 22 | 23 | import java.io.InputStream; 24 | import java.util.Collections; 25 | import java.util.List; 26 | 27 | import de.haukerehfeld.quakeinjector.InstalledPackageList; 28 | import de.haukerehfeld.quakeinjector.PackageFileList; 29 | 30 | 31 | /** 32 | * Thread worker to parse the installed maps in background 33 | */ 34 | public class InstalledMapsParser { 35 | public List parse(final InputStream in) { 36 | List files; 37 | try { 38 | files = new InstalledPackageList().read(in); 39 | } 40 | catch (java.io.FileNotFoundException e) { 41 | System.out.println("Notice: installed maps file doesn't exist yet," 42 | + " no maps installed? " + e); 43 | files = Collections.emptyList(); 44 | } 45 | catch (java.io.IOException e) { 46 | System.err.println("Error: installed maps file couldn't be loaded: " + e); 47 | e.printStackTrace(); 48 | files = Collections.emptyList(); 49 | } 50 | return files; 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/gui/BrowserLauncher.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 Eric Wasylishen 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector.gui; 21 | 22 | import java.awt.Desktop; 23 | import java.net.URI; 24 | 25 | public class BrowserLauncher { 26 | private static edu.stanford.ejalbert.BrowserLauncher fallback = null; 27 | 28 | public static void openURL(String url) { 29 | try { 30 | Desktop.getDesktop().browse(URI.create(url)); 31 | } catch (Exception e) { 32 | System.err.println("Error calling Desktop#browse(URI): " + e.getMessage()); 33 | 34 | if (fallback == null) { 35 | try { 36 | fallback = new edu.stanford.ejalbert.BrowserLauncher(); 37 | } catch (Exception e2) { 38 | System.err.println("Couldn't init browserlauncher: " + e2.getMessage()); 39 | } 40 | } 41 | 42 | if (fallback != null) { 43 | fallback.openURLinBrowser(url); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/gui/ClosePanel.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector.gui; 21 | 22 | import java.awt.GridLayout; 23 | import java.awt.event.ActionEvent; 24 | import java.awt.event.ActionListener; 25 | 26 | import javax.swing.JButton; 27 | import javax.swing.JDialog; 28 | import javax.swing.JPanel; 29 | 30 | /** 31 | * The typical okay/cancel area 32 | */ 33 | public class ClosePanel extends JPanel { 34 | public ClosePanel(final JDialog dialog, final ActionListener action) { 35 | super(); 36 | setLayout(new GridLayout(0,2)); 37 | 38 | add(makeCloseButton(dialog, "Okay", action)); 39 | add(makeCloseButton(dialog, "Cancel")); 40 | } 41 | 42 | public static JButton makeCloseButton(final JDialog dialog, 43 | final String text) { 44 | return makeCloseButton(dialog, text, null); 45 | } 46 | 47 | /** 48 | * Make a button that closes the dialog and executes another action. 49 | * 50 | * You still have to add it to the frame. 51 | * 52 | * Action may be null. 53 | */ 54 | public static JButton makeCloseButton(final JDialog dialog, 55 | final String text, 56 | final ActionListener action) { 57 | JButton okay = new JButton(text); 58 | okay.addActionListener(new ActionListener() { 59 | public void actionPerformed(ActionEvent e) { 60 | if (action != null) { 61 | action.actionPerformed(e); 62 | } 63 | dialog.setVisible(false); 64 | dialog.dispose(); 65 | } 66 | }); 67 | return okay; 68 | } 69 | 70 | 71 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/gui/ErrorEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector.gui; 21 | 22 | public interface ErrorEvent { 23 | public Object getSource(); 24 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/gui/ErrorListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector.gui; 21 | 22 | public interface ErrorListener { 23 | public void errorOccured(ErrorEvent e); 24 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/gui/JPathPanel.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector.gui; 21 | 22 | import java.awt.event.ActionEvent; 23 | import java.awt.event.ActionListener; 24 | import java.io.File; 25 | import java.util.ArrayList; 26 | 27 | import javax.swing.BoxLayout; 28 | import javax.swing.InputVerifier; 29 | import javax.swing.JButton; 30 | import javax.swing.JComponent; 31 | import javax.swing.JFileChooser; 32 | import javax.swing.JLabel; 33 | import javax.swing.JPanel; 34 | import javax.swing.JTextField; 35 | import javax.swing.event.ChangeListener; 36 | import javax.swing.event.DocumentEvent; 37 | import javax.swing.event.DocumentListener; 38 | 39 | import de.haukerehfeld.quakeinjector.ChangeListenerList; 40 | import de.haukerehfeld.quakeinjector.RelativePath; 41 | import de.haukerehfeld.quakeinjector.Utils; 42 | 43 | /** 44 | * A Panel to input paths 45 | */ 46 | public class JPathPanel extends JPanel { 47 | private final ArrayList errorListeners = new ArrayList(); 48 | private final ChangeListenerList changeListeners = new ChangeListenerList(); 49 | 50 | private final static int inputLength = 32; 51 | 52 | private File basePath; 53 | 54 | private final JTextField path; 55 | private final JLabel errorLabel; 56 | private final JButton fileChooserButton; 57 | private final JFileChooser chooser; 58 | 59 | 60 | private final Verifier check; 61 | 62 | /** 63 | * If the saved path is absolute although we have a basepath - happens on windows when the basepath is 64 | * on a different drive 65 | */ 66 | private boolean absolute = false; 67 | 68 | /** 69 | * @param filesAndOrDirectories what kind of files can be selected with the filechooser: 70 | * one of JFileChooser.DIRECTORIES_ONLY, 71 | * JFileChooser.FILES_AND_DIRECTORIES, 72 | * JFileChooser.FILES_ONLY 73 | */ 74 | public JPathPanel(Verifier check, File defaultPath, int filesAndOrDirectories) { 75 | this(check, defaultPath, null, filesAndOrDirectories); 76 | } 77 | 78 | /** 79 | * @param filesAndOrDirectories what kind of files can be selected with the filechooser: 80 | * one of JFileChooser.DIRECTORIES_ONLY, 81 | * JFileChooser.FILES_AND_DIRECTORIES, 82 | * JFileChooser.FILES_ONLY 83 | */ 84 | public JPathPanel(Verifier check, 85 | File defaultPath, 86 | File basePath, 87 | int filesAndOrDirectories) { 88 | this.check = check; 89 | this.basePath = basePath; 90 | 91 | if (defaultPath == null) { 92 | defaultPath = new File(""); 93 | } 94 | 95 | 96 | setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS)); 97 | 98 | 99 | 100 | absolute = defaultPath.isAbsolute(); 101 | 102 | this.path = new JTextField(defaultPath.toString(), inputLength); 103 | PathVerifier verifier = new PathVerifier(); 104 | path.setInputVerifier(verifier); 105 | path.getDocument().addDocumentListener(verifier); 106 | add(path); 107 | 108 | this.errorLabel = new JLabel(); 109 | add(errorLabel); 110 | 111 | this.chooser = new JFileChooser(getPath()); 112 | chooser.setFileSelectionMode(filesAndOrDirectories); 113 | 114 | this.fileChooserButton = new JButton("Select"); 115 | fileChooserButton.addActionListener(new ActionListener() { 116 | public void actionPerformed(ActionEvent e) { 117 | int returnVal = chooser.showOpenDialog(JPathPanel.this); 118 | 119 | if (returnVal == JFileChooser.APPROVE_OPTION) { 120 | File file = chooser.getSelectedFile(); 121 | 122 | setPath(file); 123 | } 124 | } 125 | }); 126 | add(fileChooserButton); 127 | } 128 | 129 | /** 130 | * Just checks if the current path is valid without notifying listeners 131 | */ 132 | public boolean verifies() { 133 | return check(); 134 | } 135 | 136 | /** 137 | * Check if current path is valid and notify listeners 138 | */ 139 | public boolean verify() { 140 | if (!check()) { 141 | notifyErrorListeners(); 142 | return false; 143 | } 144 | else { 145 | chooser.setCurrentDirectory(getPath()); 146 | notifyChangeListeners(); 147 | return true; 148 | } 149 | } 150 | 151 | 152 | private boolean check() { 153 | File f = getPath(); 154 | //System.out.println(f); 155 | errorLabel.setText(check.errorMessage(f)); 156 | return this.check.verify(f); 157 | } 158 | 159 | public void setBasePath(File basePath) { 160 | File oldFile = getPath(); 161 | boolean verifies = verifies(); 162 | this.basePath = basePath; 163 | //System.out.println("Changing basePath: " + oldFile + " to " + basePath); 164 | if (verifies) { 165 | setPath(oldFile); 166 | } 167 | //put this above the if (verifies) to change chooser to the new basedir 168 | this.chooser.setCurrentDirectory(getPath()); 169 | verify(); 170 | } 171 | 172 | public void setPath(String path) { 173 | setPath(new File(path)); 174 | } 175 | 176 | public void setPath(File path) { 177 | String pathString; 178 | if (basePath != null) { 179 | File relative = RelativePath.getRelativePath(basePath, path); 180 | //may return absolute path if no relative path possible 181 | absolute = relative.isAbsolute(); 182 | pathString = relative.toString(); 183 | } 184 | else { 185 | pathString = path.getAbsolutePath(); 186 | } 187 | 188 | this.path.setText(pathString); 189 | } 190 | 191 | /** 192 | * get a file representing what this pathpanel is pointing to 193 | */ 194 | public File getPath() { 195 | /* 196 | * Build a file object from - if set - the basepath and the textfield content 197 | */ 198 | String path = this.path.getText(); 199 | if (path == null) { 200 | path = ""; 201 | } 202 | 203 | File file; 204 | if (basePath != null && !absolute) { 205 | file = new File(basePath.getAbsolutePath() + File.separator + path); 206 | } 207 | else { 208 | file = new File(path); 209 | } 210 | return file; 211 | } 212 | 213 | 214 | public void addErrorListener(ErrorListener e) { 215 | errorListeners.add(e); 216 | } 217 | 218 | private void notifyErrorListeners() { 219 | ErrorEvent e = new SimpleErrorEvent(this); 220 | for (ErrorListener l: errorListeners) { 221 | l.errorOccured(e); 222 | } 223 | } 224 | 225 | public void addChangeListener(ChangeListener l) { 226 | changeListeners.addChangeListener(l); 227 | } 228 | 229 | private void notifyChangeListeners() { 230 | changeListeners.notifyChangeListeners(this); 231 | } 232 | 233 | /** 234 | * Hack: Because i can't call verify() from the inner class that has @Override verify(Stuff s); 235 | */ 236 | private boolean verify_() { 237 | return verify(); 238 | } 239 | 240 | 241 | 242 | public interface Verifier { 243 | public boolean verify(File file); 244 | public String errorMessage(File file); 245 | } 246 | 247 | public static class WritableDirectoryVerifier implements Verifier { 248 | public boolean verify(File f) { 249 | return (f.exists() 250 | && f.isDirectory() 251 | && f.canRead() 252 | && Utils.canWriteToDirectory(f)); 253 | } 254 | public String errorMessage(File f) { 255 | if (!f.exists()) { 256 | return "Doesn't exist!"; 257 | } 258 | else if (!f.isDirectory()) { 259 | return "Is not a directory!"; 260 | } 261 | else if (!Utils.canWriteToDirectory(f)) { 262 | return "Cannot be written to!"; 263 | } 264 | return null; 265 | } 266 | } 267 | 268 | 269 | private class PathVerifier extends InputVerifier implements DocumentListener { 270 | @Override 271 | public void insertUpdate(DocumentEvent e) { 272 | verify_(); 273 | } 274 | @Override 275 | public void removeUpdate(DocumentEvent e) { 276 | verify_(); 277 | } 278 | @Override 279 | public void changedUpdate(DocumentEvent e) { 280 | verify_(); 281 | } 282 | @Override 283 | public boolean verify(JComponent input) { 284 | return verify_(); 285 | } 286 | @Override 287 | public boolean shouldYieldFocus(JComponent input) { 288 | verify_(); 289 | return true; 290 | } 291 | } 292 | 293 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/gui/LabelFieldPanel.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector.gui; 21 | 22 | import javax.swing.BoxLayout; 23 | import javax.swing.JComponent; 24 | import javax.swing.JLabel; 25 | import javax.swing.JPanel; 26 | import javax.swing.SwingConstants; 27 | 28 | /** 29 | * A Panel with a label and a field 30 | */ 31 | public class LabelFieldPanel extends JPanel { 32 | public LabelFieldPanel(String labelText, JComponent field) { 33 | setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS)); 34 | 35 | JLabel label = new JLabel(labelText, SwingConstants.RIGHT); 36 | 37 | label.setLabelFor(field); 38 | add(label); 39 | add(field); 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/gui/LookAndFeelDefaults.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector.gui; 21 | 22 | import javax.swing.BorderFactory; 23 | import javax.swing.JPanel; 24 | import javax.swing.border.Border; 25 | 26 | public class LookAndFeelDefaults extends JPanel { 27 | public static final int FRAMEPADDING = 7; 28 | 29 | public static final Border PADDINGBORDER = BorderFactory.createEmptyBorder(FRAMEPADDING, 30 | FRAMEPADDING, 31 | 0, 32 | FRAMEPADDING); 33 | 34 | public static final int DIALOGDESCRIPTIONMARGIN = 7; 35 | 36 | public static final Border DIALOGDESCRIPTIONBORDER = BorderFactory.createEmptyBorder(DIALOGDESCRIPTIONMARGIN, 37 | 0, 38 | DIALOGDESCRIPTIONMARGIN, 39 | 0); 40 | 41 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/gui/OkayCancelApplyPanel.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector.gui; 21 | 22 | import java.awt.GridBagConstraints; 23 | import java.awt.GridBagLayout; 24 | import java.awt.Insets; 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | import javax.swing.Box; 29 | import javax.swing.JButton; 30 | import javax.swing.JComponent; 31 | import javax.swing.JPanel; 32 | 33 | public class OkayCancelApplyPanel extends JPanel { 34 | private static final Insets LEFTBORDER = new Insets(LookAndFeelDefaults.FRAMEPADDING, 35 | LookAndFeelDefaults.FRAMEPADDING, 36 | LookAndFeelDefaults.FRAMEPADDING, 37 | LookAndFeelDefaults.FRAMEPADDING); 38 | 39 | private static final Insets OTHERBORDER = new Insets(LookAndFeelDefaults.FRAMEPADDING, 40 | 0, 41 | LookAndFeelDefaults.FRAMEPADDING, 42 | LookAndFeelDefaults.FRAMEPADDING); 43 | 44 | private final JButton okay; 45 | private final JButton cancel; 46 | private final JButton apply; 47 | 48 | public OkayCancelApplyPanel(JButton okay, JButton cancel, JButton apply, boolean useApply) { 49 | setLayout(new GridBagLayout()); 50 | 51 | this.okay = okay; 52 | this.cancel = cancel; 53 | this.apply = apply; 54 | 55 | 56 | List components = new ArrayList(3); 57 | 58 | components.add(okay); 59 | 60 | if (cancel != null) { 61 | components.add(cancel); 62 | } 63 | 64 | if (apply != null) { 65 | apply.setEnabled(false); 66 | components.add(apply); 67 | } 68 | 69 | OkayCancelApplyPanel.addRow(this, 0, components); 70 | } 71 | 72 | public void setApplyEnabled(boolean enabled) { 73 | this.apply.setEnabled(enabled); 74 | } 75 | 76 | /** 77 | * Add some components right aligned to a gridbaglayout container 78 | * at a specific row, with proper borders. 79 | */ 80 | public static void addRow(final java.awt.Container container, 81 | final int row, 82 | final List components) { 83 | //right aligned 84 | container.add(Box.createHorizontalGlue(), new LeftConstraints()); 85 | 86 | int col = 1; 87 | for (JComponent c: components) { 88 | final int col_ = col; 89 | container.add(c, new RightConstraints() {{ gridx = col_; gridy = row; }}); 90 | col++; 91 | } 92 | } 93 | 94 | static class LeftConstraints extends GridBagConstraints {{ 95 | insets = LEFTBORDER; 96 | gridx = 0; 97 | weightx = 1; 98 | }} 99 | 100 | static class RightConstraints extends GridBagConstraints {{ 101 | insets = OTHERBORDER; 102 | anchor = LINE_END; 103 | fill = NONE; 104 | }} 105 | 106 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/gui/ProgressListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector.gui; 21 | 22 | /** 23 | * A class that gets called everytime something that progresses is able to 24 | */ 25 | public interface ProgressListener { 26 | /** 27 | * @param progress Progress from 0 (did nothing) to 1 (completed) 28 | */ 29 | public void reportProgress(float progress); 30 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/gui/ProgressPopup.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector.gui; 21 | 22 | //import java.awt.*; 23 | import java.awt.GridBagConstraints; 24 | import java.awt.GridBagLayout; 25 | import java.awt.event.ActionListener; 26 | 27 | import javax.swing.JButton; 28 | import javax.swing.JDialog; 29 | import javax.swing.JFrame; 30 | import javax.swing.JProgressBar; 31 | 32 | public class ProgressPopup extends JDialog { 33 | private JProgressBar progress; 34 | private JButton cancelButton; 35 | private String description; 36 | 37 | public ProgressPopup(String description, ActionListener cancel, JFrame frame) { 38 | super(frame, description, true); 39 | setLocationRelativeTo(frame); 40 | 41 | this.description = description; 42 | 43 | setLayout(new GridBagLayout()); 44 | 45 | progress = new JProgressBar(); 46 | progress.setString(description); 47 | progress.setValue(0); 48 | progress.setIndeterminate(true); 49 | progress.setStringPainted(true); 50 | 51 | add(progress, new GridBagConstraints() {{ 52 | anchor = CENTER; 53 | gridx = 0; 54 | fill = BOTH; 55 | weightx = 1; 56 | }}); 57 | 58 | JButton cancelButton; 59 | cancelButton = new JButton("Cancel"); 60 | cancelButton.addActionListener(cancel); 61 | add(cancelButton, new GridBagConstraints() {{ 62 | anchor = CENTER; 63 | fill = BOTH; 64 | gridy = 1; 65 | }}); 66 | } 67 | 68 | /** 69 | * Change progress 70 | * 71 | * @param percent percent [0, 100] 72 | */ 73 | public void setProgress(int percent) { 74 | progress.setIndeterminate(false); 75 | progress.setValue(percent); 76 | progress.setString(progressString(description, percent)); 77 | 78 | } 79 | 80 | public void close() { 81 | setVisible(false); 82 | dispose(); 83 | } 84 | 85 | public static String progressString(String description, String status) { 86 | return description + ": " + status; 87 | } 88 | 89 | public static String progressString(String description, int progress) { 90 | return progressString(description, progress + "%"); 91 | } 92 | 93 | } 94 | 95 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/gui/ScrollablePanel.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector.gui; 21 | 22 | import java.awt.Dimension; 23 | import java.awt.FlowLayout; 24 | import java.awt.GridBagConstraints; 25 | import java.awt.GridBagLayout; 26 | import java.text.DateFormat; 27 | import java.text.SimpleDateFormat; 28 | import java.util.Date; 29 | import java.util.List; 30 | import java.util.ArrayList; 31 | import java.util.TimeZone; 32 | 33 | import javax.swing.JEditorPane; 34 | import javax.swing.text.html.HTMLEditorKit; 35 | import javax.swing.text.html.StyleSheet; 36 | import javax.swing.text.Style; 37 | import java.util.Enumeration; 38 | 39 | 40 | import javax.swing.JLabel; 41 | import javax.swing.JPanel; 42 | import javax.swing.JScrollPane; 43 | import javax.swing.SwingConstants; 44 | import javax.swing.event.ChangeEvent; 45 | import javax.swing.event.ChangeListener; 46 | 47 | import javax.swing.ImageIcon; 48 | import javax.swing.border.EmptyBorder; 49 | import java.net.URL; 50 | 51 | import javax.swing.Scrollable; 52 | import java.awt.Rectangle; 53 | 54 | 55 | import javax.swing.event.HyperlinkListener; 56 | import javax.swing.event.HyperlinkEvent; 57 | 58 | import javax.swing.SwingWorker; 59 | import java.util.concurrent.Future; 60 | 61 | /** 62 | * JPanel that supports Scrollable 63 | * @see javax.swing.Scrollable 64 | */ 65 | public class ScrollablePanel extends JPanel implements Scrollable { 66 | private int scrollUnitIncrement; 67 | private int scrollBlockincrement; 68 | private boolean tracksWidth; 69 | private boolean tracksHeight; 70 | 71 | public ScrollablePanel(int scrollUnitIncrement, 72 | int scrollBlockincrement, 73 | boolean tracksWidth, 74 | boolean tracksHeight) { 75 | this.scrollUnitIncrement = scrollUnitIncrement; 76 | this.scrollBlockincrement = scrollBlockincrement; 77 | this.tracksWidth = tracksWidth; 78 | this.tracksHeight = tracksHeight; 79 | } 80 | 81 | /** 82 | * Defaults for viewport size tracking: track width, but not height 83 | */ 84 | public ScrollablePanel(int scrollUnitIncrement, 85 | int scrollBlockincrement) { 86 | this(scrollUnitIncrement, scrollBlockincrement, true, false); 87 | } 88 | 89 | 90 | @Override 91 | public Dimension getPreferredScrollableViewportSize() { 92 | return getPreferredSize(); 93 | } 94 | 95 | @Override 96 | public int getScrollableUnitIncrement(Rectangle visibleRect, 97 | int orientation, 98 | int direction) { 99 | return scrollUnitIncrement; 100 | } 101 | 102 | @Override 103 | public int getScrollableBlockIncrement(Rectangle visibleRect, 104 | int orientation, 105 | int direction) { 106 | return scrollBlockincrement; 107 | } 108 | 109 | @Override 110 | public boolean getScrollableTracksViewportWidth() { return tracksWidth; } 111 | @Override 112 | public boolean getScrollableTracksViewportHeight() { return tracksHeight; } 113 | } 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/gui/SimpleErrorEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector.gui; 21 | 22 | public class SimpleErrorEvent implements ErrorEvent { 23 | private Object source; 24 | 25 | public SimpleErrorEvent(Object source) { 26 | this.source = source; 27 | } 28 | 29 | public Object getSource() { 30 | return source; 31 | } 32 | } -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/packagelist/model/Column.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector.packagelist.model; 21 | import de.haukerehfeld.quakeinjector.Package; 22 | import java.util.Date; 23 | 24 | /** 25 | * Enum for the table columns. Saves all info about them. 26 | */ 27 | public enum Column { 28 | INSTALLED("") { 29 | public Object getData(Package p) { 30 | return Boolean.valueOf(p.isInstalled()); 31 | } 32 | public Class getColumnClass() { return Boolean.class; } 33 | }, 34 | NAME("Name") { 35 | public Object getData(Package p) { 36 | return p.getId(); 37 | } 38 | 39 | public Class getColumnClass() { return String.class; } 40 | }, 41 | TITLE("Title") { 42 | public Object getData(Package p) { 43 | return p.getTitle(); 44 | } 45 | public Class getColumnClass() { return String.class; } 46 | }, 47 | AUTHOR("Author") { 48 | public Object getData(Package p) { 49 | return p.getAuthor(); 50 | } 51 | public Class getColumnClass() { return String.class; } 52 | }, 53 | RELEASEDATE("Released") { 54 | public Object getData(Package p) { 55 | return p.getDate(); 56 | } 57 | public Class getColumnClass() { return Date.class; } 58 | }, 59 | RATING("Rating") { 60 | public Object getData(Package p) { 61 | return p.getRating(); 62 | } 63 | public Class getColumnClass() { return Package.Rating.class; } 64 | }, 65 | NORMALIZEDUSERSRATING("Users'") { 66 | public Object getData(Package p) { 67 | return String.format("%.2f", p.getNormalizedUsersRating()); 68 | } 69 | public Class getColumnClass() { return Integer.class; } 70 | } 71 | ; 72 | 73 | 74 | public String header; 75 | 76 | private Column(String header) { 77 | this.header = header; 78 | } 79 | 80 | public abstract Object getData(Package p); 81 | 82 | public abstract Class getColumnClass(); 83 | 84 | public static int getColumnNumber(Column c) { 85 | return java.util.Arrays.binarySearch(values(), c); 86 | } 87 | 88 | public static Column getColumn(int column) { 89 | Column[] values = values(); 90 | if (values.length <= column) { 91 | throw new RuntimeException("Unknown Column"); 92 | } 93 | return values[column]; 94 | } 95 | 96 | public static int count() { 97 | return values().length; 98 | } 99 | } 100 | 101 | 102 | -------------------------------------------------------------------------------- /src/main/java/de/haukerehfeld/quakeinjector/packagelist/model/PackageListModel.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009 Hauke Rehfeld 3 | 4 | 5 | This file is part of QuakeInjector. 6 | 7 | QuakeInjector is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | QuakeInjector is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with QuakeInjector. If not, see . 19 | */ 20 | package de.haukerehfeld.quakeinjector.packagelist.model; 21 | 22 | import java.awt.Component; 23 | import java.awt.FlowLayout; 24 | import java.util.ArrayList; 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | import java.util.List; 28 | import java.util.regex.Pattern; 29 | 30 | import javax.swing.ImageIcon; 31 | import javax.swing.JLabel; 32 | import javax.swing.JPanel; 33 | import javax.swing.JTable; 34 | import javax.swing.RowFilter; 35 | import javax.swing.border.EmptyBorder; 36 | import javax.swing.event.ChangeEvent; 37 | import javax.swing.event.ChangeListener; 38 | import javax.swing.event.TableModelEvent; 39 | import javax.swing.table.AbstractTableModel; 40 | import javax.swing.table.TableCellRenderer; 41 | import javax.swing.table.TableColumnModel; 42 | 43 | import de.haukerehfeld.quakeinjector.ChangeListenerList; 44 | import de.haukerehfeld.quakeinjector.PackageList; 45 | import de.haukerehfeld.quakeinjector.Utils; 46 | import de.haukerehfeld.quakeinjector.Package; 47 | 48 | public class PackageListModel extends AbstractTableModel implements ChangeListener { 49 | private ChangeListenerList listeners = new ChangeListenerList(); 50 | 51 | private PackageList data; 52 | 53 | 54 | public PackageListModel(PackageList data) { 55 | setMapList(data); 56 | } 57 | 58 | public void size(JTable table) { 59 | TableColumnModel m = table.getColumnModel(); 60 | 61 | m.getColumn(Column.getColumnNumber(Column.NAME)).setPreferredWidth(80); 62 | m.getColumn(Column.getColumnNumber(Column.TITLE)).setPreferredWidth(150); 63 | m.getColumn(Column.getColumnNumber(Column.AUTHOR)).setPreferredWidth(100); 64 | m.getColumn(Column.getColumnNumber(Column.INSTALLED)).setResizable(false); 65 | m.getColumn(Column.getColumnNumber(Column.INSTALLED)).setMaxWidth(16); 66 | m.getColumn(Column.getColumnNumber(Column.INSTALLED)).setMinWidth(16); 67 | 68 | int ratingSize = 5 * (RatingRenderer.ICONSIZE + RatingRenderer.HORIZONTALGAP) 69 | + RatingRenderer.HORIZONTALGAP; 70 | m.getColumn(Column.getColumnNumber(Column.RATING)).setMinWidth(ratingSize); 71 | m.getColumn(Column.getColumnNumber(Column.RATING)).setMaxWidth(ratingSize); 72 | m.getColumn(Column.getColumnNumber(Column.RATING)).setResizable(false); 73 | 74 | m.getColumn(Column.getColumnNumber(Column.NORMALIZEDUSERSRATING)).setMinWidth(48); 75 | m.getColumn(Column.getColumnNumber(Column.NORMALIZEDUSERSRATING)).setMaxWidth(48); 76 | m.getColumn(Column.getColumnNumber(Column.NORMALIZEDUSERSRATING)).setResizable(false); 77 | 78 | m.getColumn(Column.getColumnNumber(Column.RELEASEDATE)).setMinWidth(75); 79 | m.getColumn(Column.getColumnNumber(Column.RELEASEDATE)).setPreferredWidth(80); 80 | //m.getColumn(Column.getColumnNumber(Column.RELEASEDATE)).setMaxWidth(80); 81 | 82 | } 83 | 84 | // public List getPackageList() { 85 | // return this.data; 86 | // } 87 | 88 | 89 | public void setMapList(PackageList data) { 90 | if (this.data != null) { 91 | //remove from all old 92 | this.data.removeChangeListener(this); 93 | for (Package map: this.data) { 94 | map.removeChangeListener(this); 95 | } 96 | } 97 | 98 | data.sort(); 99 | 100 | //add to new 101 | this.data = data; 102 | data.addChangeListener(this); 103 | 104 | for (Package map: data) { 105 | map.addChangeListener(this); 106 | } 107 | 108 | fireTableChanged(new TableModelEvent(this)); 109 | } 110 | 111 | public int getColumnCount() { 112 | return Column.count(); 113 | } 114 | 115 | @Override 116 | public String getColumnName(int col) { 117 | return Column.getColumn(col).header; 118 | } 119 | 120 | /* 121 | * data 122 | */ 123 | @Override 124 | public int getRowCount() { 125 | return data.size(); 126 | } 127 | 128 | 129 | public Object getColumnData(int col, Package info) { 130 | return Column.getColumn(col).getData(info); 131 | } 132 | 133 | public boolean isPackageInstalled(Package info){ 134 | return (boolean) getColumnData(0, info); 135 | } 136 | 137 | @Override 138 | public Class getColumnClass(int c) { 139 | return Column.getColumn(c).getColumnClass(); 140 | } 141 | 142 | @Override 143 | public Object getValueAt(int row, int col) { 144 | return getColumnData(col, data.get(row)); 145 | } 146 | 147 | @Override 148 | public boolean isCellEditable(int row, int col) { 149 | return false; 150 | } 151 | 152 | public Package getPackage(int row) { 153 | return data.get(row); 154 | } 155 | 156 | public void addChangeListener(ChangeListener l) { 157 | listeners.addChangeListener(l); 158 | } 159 | 160 | public void stateChanged(ChangeEvent e) { 161 | if (e.getSource() instanceof PackageList) { 162 | setMapList((PackageList) e.getSource()); 163 | listeners.notifyChangeListeners(e.getSource()); 164 | return; 165 | } 166 | 167 | if (e.getSource() instanceof Package) { 168 | Package r = (Package) e.getSource(); 169 | listeners.notifyChangeListeners(r); 170 | int i = data.indexOf(r); 171 | super.fireTableRowsUpdated(i, i); 172 | return; 173 | } 174 | 175 | throw new RuntimeException("didn't recognise what changed!"); 176 | } 177 | 178 | 179 | /** 180 | * Update the row filter regular expression from the expression in 181 | * the text box. 182 | */ 183 | public RowFilter filter(final String filterText) { 184 | final int[] columnsToCheck = { Column.getColumnNumber(Column.NAME), 185 | Column.getColumnNumber(Column.AUTHOR), 186 | Column.getColumnNumber(Column.TITLE), 187 | Column.getColumnNumber(Column.RELEASEDATE) 188 | }; 189 | 190 | String[] filterTexts = filterText.split(" "); 191 | 192 | final List patterns = new ArrayList(filterTexts.length); 193 | for (String filter: filterTexts) { 194 | try { 195 | patterns.add(Pattern.compile(".*" + filter + ".*", 196 | Pattern.CASE_INSENSITIVE + Pattern.UNICODE_CASE)); 197 | } catch (java.util.regex.PatternSyntaxException e) { 198 | continue; 199 | } 200 | } 201 | 202 | RowFilter rf = new RowFilter() { 203 | @Override 204 | public boolean include(Entry entry) { 205 | //match all patters in at least one column 206 | for (Pattern pattern: patterns) { 207 | boolean matches = false; 208 | for (int i: columnsToCheck) { 209 | if (pattern.matcher(entry.getStringValue(i)).matches()) { 210 | matches = true; 211 | } 212 | } 213 | if (!matches) { 214 | return false; 215 | } 216 | } 217 | return true; 218 | } 219 | }; 220 | 221 | return rf; 222 | } 223 | 224 | public static class RatingRenderer extends JPanel implements TableCellRenderer { 225 | private static final int HORIZONTALGAP = 2; 226 | private static final int ICONSIZE = 8; 227 | 228 | private ImageIcon activeIcon; 229 | private ImageIcon inactiveIcon; 230 | 231 | private List ratingLabels = new ArrayList(5); 232 | 233 | public RatingRenderer() { 234 | super(); 235 | 236 | try { 237 | activeIcon = Utils.createImageIcon("/star_spirit_8.png", "activeStar"); 238 | inactiveIcon = Utils.createImageIcon("/star_spirit_8_inactive.png", "inactiveStar"); 239 | } 240 | catch (java.io.IOException e) { 241 | System.err.println("WARNING: Couldn't load rating image!"); 242 | } 243 | 244 | EmptyBorder border = new EmptyBorder(0,0,0,0); 245 | setBorder(border); 246 | ((FlowLayout) getLayout()).setHgap(HORIZONTALGAP); 247 | ((FlowLayout) getLayout()).setVgap(4); 248 | 249 | for (int i = 0; i < 5; ++i) { 250 | JLabel label = new JLabel(activeIcon); 251 | label.setDisabledIcon(inactiveIcon); 252 | label.setBorder(border); 253 | label.setOpaque(false); 254 | add(label); 255 | ratingLabels.add(label); 256 | } 257 | } 258 | 259 | public Component getTableCellRendererComponent(JTable table, 260 | Object value, 261 | boolean isSelected, 262 | boolean hasFocus, 263 | int row, 264 | int column) { 265 | if (value instanceof Package.Rating) { 266 | Package.Rating rating = (Package.Rating) value; 267 | int i = 0; 268 | for (JLabel label: ratingLabels) { 269 | boolean enabled = true; 270 | if (i >= rating.getRating()) { 271 | enabled = false; 272 | } 273 | label.setEnabled(enabled); 274 | ++i; 275 | } 276 | } 277 | return this; 278 | } 279 | } 280 | 281 | // /* 282 | // * Don't need to implement this method unless your table's 283 | // * data can change. 284 | // */ 285 | // public void setValueAt(Object value, int row, int col) { 286 | // data[row][col] = value; 287 | // fireTableCellUpdated(row, col); 288 | // }{ 289 | 290 | } 291 | -------------------------------------------------------------------------------- /src/main/resources/Inject2.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrehfeld/QuakeInjector/65fe5924c7703038e7e2eedc5ae2e17cda19560a/src/main/resources/Inject2.ico -------------------------------------------------------------------------------- /src/main/resources/Inject2_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrehfeld/QuakeInjector/65fe5924c7703038e7e2eedc5ae2e17cda19560a/src/main/resources/Inject2_16.png -------------------------------------------------------------------------------- /src/main/resources/Inject2_16_16colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrehfeld/QuakeInjector/65fe5924c7703038e7e2eedc5ae2e17cda19560a/src/main/resources/Inject2_16_16colors.png -------------------------------------------------------------------------------- /src/main/resources/Inject2_16_256colors..png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrehfeld/QuakeInjector/65fe5924c7703038e7e2eedc5ae2e17cda19560a/src/main/resources/Inject2_16_256colors..png -------------------------------------------------------------------------------- /src/main/resources/Inject2_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrehfeld/QuakeInjector/65fe5924c7703038e7e2eedc5ae2e17cda19560a/src/main/resources/Inject2_256.png -------------------------------------------------------------------------------- /src/main/resources/Inject2_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrehfeld/QuakeInjector/65fe5924c7703038e7e2eedc5ae2e17cda19560a/src/main/resources/Inject2_32.png -------------------------------------------------------------------------------- /src/main/resources/Inject2_32_16colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrehfeld/QuakeInjector/65fe5924c7703038e7e2eedc5ae2e17cda19560a/src/main/resources/Inject2_32_16colors.png -------------------------------------------------------------------------------- /src/main/resources/Inject2_32_256colors..png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrehfeld/QuakeInjector/65fe5924c7703038e7e2eedc5ae2e17cda19560a/src/main/resources/Inject2_32_256colors..png -------------------------------------------------------------------------------- /src/main/resources/Inject2_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrehfeld/QuakeInjector/65fe5924c7703038e7e2eedc5ae2e17cda19560a/src/main/resources/Inject2_48.png -------------------------------------------------------------------------------- /src/main/resources/Inject2_48_16colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrehfeld/QuakeInjector/65fe5924c7703038e7e2eedc5ae2e17cda19560a/src/main/resources/Inject2_48_16colors.png -------------------------------------------------------------------------------- /src/main/resources/Inject2_48_256colors..png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrehfeld/QuakeInjector/65fe5924c7703038e7e2eedc5ae2e17cda19560a/src/main/resources/Inject2_48_256colors..png -------------------------------------------------------------------------------- /src/main/resources/star_spirit_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrehfeld/QuakeInjector/65fe5924c7703038e7e2eedc5ae2e17cda19560a/src/main/resources/star_spirit_12.png -------------------------------------------------------------------------------- /src/main/resources/star_spirit_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrehfeld/QuakeInjector/65fe5924c7703038e7e2eedc5ae2e17cda19560a/src/main/resources/star_spirit_8.png -------------------------------------------------------------------------------- /src/main/resources/star_spirit_8_inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrehfeld/QuakeInjector/65fe5924c7703038e7e2eedc5ae2e17cda19560a/src/main/resources/star_spirit_8_inactive.png --------------------------------------------------------------------------------