Skip to content
This repository has been archived by the owner on Mar 18, 2024. It is now read-only.

Basic issue of understanding #164

Closed
Optimaloptic opened this issue Feb 10, 2022 · 25 comments
Closed

Basic issue of understanding #164

Optimaloptic opened this issue Feb 10, 2022 · 25 comments
Labels
question Further information is requested

Comments

@Optimaloptic
Copy link

Hello Mordechaim,

I am a programming novice (at best) and I've been tasked with implementing automatic updates with update4j. I have no idea on how to make update4j ignore files which are older than the downloaded and installed version of the application. Is there any way to do this?

@mordechaim mordechaim added the question Further information is requested label Feb 10, 2022
@mordechaim
Copy link
Contributor

Are you saying that your configuration might contain file references that are older than the current version? Why would you do so?

@Optimaloptic
Copy link
Author

Optimaloptic commented Feb 10, 2022

Thanks for the swift response. In case a client would be on a new installation, and have to perform some incremental (and sequentially dependant) updates, I would like to keep the old update files and have them available (and then of course delete the old update files locally).

@mordechaim
Copy link
Contributor

So that's the other way around, the configuration has some new files but not all of them changed over the last update, correct?

Update4j supports this out of the box, it will only update the changed files. It uses the checksum to check if a file changed.

@Optimaloptic
Copy link
Author

I tried what you're talking about previously, and that worked fine for that particular case, but if I were to download e.g. 20 .tar files and unpack & install them, and then delete them (to not take up unnecessary space clientside), update4j would probably download the .tars again with the .update() command, right? Is there a way to circumvent this behavior in some way?

@mordechaim
Copy link
Contributor

mordechaim commented Feb 10, 2022

Right, update4j won't have a way to check this on itself, but if you want to really go ballistic you can provide a custom UpdateHandler and do your own checks in shouldCheckForUpdate().

@mordechaim
Copy link
Contributor

mordechaim commented Feb 10, 2022

The better way is to download the files directly instead of tarballs. Or, perhaps, leave the tar files for references for update4j to check against

@Optimaloptic
Copy link
Author

Alright, I guess I'll have to switch my approach to pure files instead in that case. On the same topic, since the updates are in some cases to be sequentially applied (as would be the case when appending text files with other text files) is there any way to stop .update() from downloading all the files at the same time, and instead only download a set of files and then trigger an install? Or would this perhaps also have to include modding the UpdateHandler?

@mordechaim
Copy link
Contributor

Can you tell me a concrete example of your problem?

@Optimaloptic
Copy link
Author

Apologies if I'm not concrete enough, I guess I lack certain lingo and knowledge in order to explain my problem better.

Say that a new client downloads the program, with version 1.0. However, 10 new verisons have succeeded the first version, and we are now on 10.0. I would like this client to download version 2.0, install the update, download version 3.0, install that one... all the way to version 10.0. Is this feasible?

@mordechaim
Copy link
Contributor

Completely feasible and works with update4j, but you cannot use the default bootstrap via the CLI.

The only thing it cares about is a config file. In your bootstrap, you need to find the correct config and use that to update. You can use your logic to locate the config according to the currently installed version. You might perhaps run a loop to update/install until you reach your desired version.

@Optimaloptic
Copy link
Author

Optimaloptic commented Feb 11, 2022

Thank you for the answers, I've managed to create a working update implementation. I have one more problem. I'm trying to replace a running .jar and open up an updated instance of it. The first launch() works fine, but once I try to launch my updated application via the .launch() function in my IDE, it throws a java.nio.file.FileSystemException.

I'm guessing I have to unload the .jar from the classpath in some way, or kill the process somehow?

@mordechaim
Copy link
Contributor

Please post your stack trace and your operating system.

Also a bit of background roughly how your bootstrap process works will help

@Optimaloptic
Copy link
Author

Optimaloptic commented Feb 11, 2022

Windows 10

java.nio.file.FileSystemException: C:\Users\Ante\IdeaProjects\bootstrap\jframe.jar -> C:\Users\Ante\IdeaProjects\ClientUpdateLibraryMode\bootstrap\1072319098167854436.tmp: The process cannot access the file because it is being used by another process

at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:86)
at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97)
at sun.nio.fs.WindowsFileCopy.move(WindowsFileCopy.java:387)
at sun.nio.fs.WindowsFileSystemProvider.move(WindowsFileSystemProvider.java:287)
at java.nio.file.Files.move(Files.java:1395)
at org.update4j.util.FileUtils.verifyNotLocked(FileUtils.java:222)
at org.update4j.ConfigImpl.completeDownloads(ConfigImpl.java:248)
at org.update4j.ConfigImpl.doUpdate(ConfigImpl.java:179)
at org.update4j.Configuration.update(Configuration.java:1006)
at org.update4j.Configuration.update(Configuration.java:938)
at org.update4j.Configuration.update(Configuration.java:852)
at MainClass.start(MainClass.java:56)
at MainClass.main(MainClass.java:30)

Background:
Upon running, the bootstrap checks if there is a new version of the program available from a remote .txt file. After selecting e.g. 2.0, the bootstrap downloads jframe.jar, and then launches the configuration. The bootstrap, which is stuck in a while loop, will once again perform the update check. If I select 2.0 once again, there is no issue, but if I were to select another version, say 3.0, the IDE throws the error.

What I would like to do is shut down the business application, and then once the update has been applied, boot the .jar again using .launch().

@mordechaim
Copy link
Contributor

Ok, so to avoid these issues you need to release the file locks. Here's how to do it in update4j:

var loader = new DynamicClassLoader()
Thread.currentThread().setContextClassLoader(loader)
config.launch() // first launch

// Later to unlock files from previous launch
loader.close()

// On next launch you can no longer use a closed loader, so create again
var loader2 = new DynamicClassLoader()
Thread.currentThread().setContextClassLoader(loader2)
otherConfig.launch() // second launch

@Optimaloptic
Copy link
Author

Optimaloptic commented Feb 11, 2022

Hi again, tried the above method, but I seem to be still getting the same error. loader.close() doesn't seem to be doing anything, the .jar just keeps on running (for further context, I am running on Java 8, and the dependency that I had to import for DynamicClassLoader is Clojure 1.8.0).

This is my code:

        DynamicClassLoader loader = new DynamicClassLoader();
            Thread.currentThread().setContextClassLoader(loader);

            checkForUpdate();
            config.update();
            config.launch();

            checkForUpdate();
            config.update();
            loader.close();

            DynamicClassLoader loader2 = new DynamicClassLoader();
            Thread.currentThread().setContextClassLoader(loader2);
            config.launch();

checkForUpdate prompts the user to select a version, which in turn changes the configUrl to the one matching the user's choice.

@mordechaim
Copy link
Contributor

mordechaim commented Feb 11, 2022

close() doesn't shut down the program; it will just close the file stream and let the JVM read instructions from RAM only, essentially unlocking the file.

You need to close before updating the 2nd time because that's when the file needs to be writable.

@Optimaloptic
Copy link
Author

Optimaloptic commented Feb 11, 2022

First off, I appreciate your patience and the fact that you keep answering my questions.

Secondly, I feel like this is where my lack of programming experience kicks in, so apologies if I fail to grasp some basic concepts. I tried the following code segment with the same result:

` DynamicClassLoader loader = new DynamicClassLoader();
Thread.currentThread().setContextClassLoader(loader);

    checkForUpdate();
    config.update();
    config.launch();
    loader.close();

    checkForUpdate();
    config.update();

    DynamicClassLoader loader2 = new DynamicClassLoader();
    Thread.currentThread().setContextClassLoader(loader2);
    config.launch();`

The original application starts twice, but I get the same exception the next time that the loop is ran. I'm a bit unsure of what to do after loader.close() is ran, is it possible to completely shut down the application after doing loader.close(), as that would be optimal?

@mordechaim
Copy link
Contributor

So now you were successful to start twice, kudos!

Basically, every time the loop runs you need to close the old and create a new loader inside the loop, don't hardcode this, it was just an example.

@Optimaloptic
Copy link
Author

The .jar that is picked first just keeps launching infinitely once it's in a loop, trying to do config.update() containing the new .jar just produces the same error as earlier.

` DynamicClassLoader loader = new DynamicClassLoader();
Thread.currentThread().setContextClassLoader(loader);

        checkForUpdate();
        config.update();
        config.launch();
        loader.close();

        TimeUnit.SECONDS.sleep(1);`

The file still seems to be locked in that case?

@mordechaim
Copy link
Contributor

Can you post the full code snippet with the loop and everything?

@Optimaloptic
Copy link
Author

Optimaloptic commented Feb 11, 2022

That is actually the entire loop, I simply omitted the while loop surrounding it 😄

`
public void start() throws Throwable {

    while (true) {
        DynamicClassLoader loader = new DynamicClassLoader();
        Thread.currentThread().setContextClassLoader(loader);

        checkForUpdate();
        config.update();
        config.launch();
        loader.close();

        System.out.println("Update complete.");

        TimeUnit.SECONDS.sleep(1);
    }

}`

@mordechaim
Copy link
Contributor

I don't see any logic swapping the config to a new version. You just keep updating and launching the same config, at least in the code above what I can tell.

@Optimaloptic
Copy link
Author

Optimaloptic commented Feb 11, 2022

The checkForUpdate() method prompts the user to select a version and sets the configUrl accordingly:

` private void updatePrompt() throws IOException {
Scanner prompt = new Scanner(System.in);
System.out.println("New version available, would you like to update? (Y/N)");
String choice = prompt.nextLine();
if (choice.toLowerCase().equals("y")) {
selectUpdateVersion();
}
}

private void selectUpdateVersion() throws IOException {
    System.out.println("Which version would you like to update to?");
    System.out.println("Current version: " + clientVersion);
    System.out.println("Newer versions: ");
    Scanner newVer = new Scanner(versionFile.openStream());
    Scanner sc = new Scanner(System.in);

    while (newVer.hasNext()) {
        if (newVer.next().split(";")[0].equals(clientVersion)) {
            while (newVer.hasNext()) {
                System.out.println(newVer.next());
            }
        }
    }

    versionChoice = sc.nextLine();
    switch (versionChoice) {
        case "2.0":
            configUrl = new URL("x");
            break;
        case "3.0":
            configUrl = new URL("x");
            break;
        default:
            System.out.println("Wrong input.");
    }


}


public void checkForUpdate() throws Throwable {
    Scanner sc = new Scanner(versionFile.openStream());
    config = null;


    while (sc.hasNext()) {
        String current = sc.next();
        System.out.println(current);

        if (current.split(";")[0].equals(clientVersion)) {
            if (sc.hasNext()) {
                updatePrompt();
            }
        }
    }


    try {
        InputStreamReader in = new InputStreamReader(configUrl.openStream(), StandardCharsets.UTF_8);

        try {

            config = Configuration.read(in);


        } catch (Throwable var10) {
            try {
                in.close();
            } catch (Throwable var9) {
                var10.addSuppressed(var9);
            }

            throw var10;
        } finally {
            clientVersion = versionChoice;
        }

    } catch (IOException var12) {
        System.err.println("Could not load remote config, falling back to local.");
        BufferedReader in = Files.newBufferedReader(Paths.get("business/config.xml"));

        try {
            config = Configuration.read(in);
        } catch (Throwable var11) {
            if (in != null) {
                try {
                    in.close();
                } catch (Throwable var8) {
                    var11.addSuppressed(var8);
                }
            }

            throw var11;
        }

        if (in != null) {
            in.close();
        }
    }

}

`

@mordechaim
Copy link
Contributor

Try returning the config variable in checkForUpdate and in the loop do config = checkForUpdate()

This is Java 101

@Optimaloptic
Copy link
Author

Tried the above, but still got the same error. Thank you for your time, might help someone else with similar issues in the future.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants