JDownloader Community - Appwork GmbH
 

Reply
 
Thread Tools Display Modes
  #1  
Old 08.12.2024, 12:56
rabidman rabidman is offline
DSL Light User
 
Join Date: Dec 2024
Location: San Jose, California
Posts: 33
Default Moving packages and files after completion

I'm hoping to move packages to a separate folder after completion. I know the feature does not exist in JDownload 2. I found a script at https://board.jdownloader.org/showpo...7&postcount=29 .

However, it moves all download files to a single directory, without preserving any of the original directory structure. Is there any way to fix that ?

If this was C or C++, I wouldn't have any issue doing it myself, but I'm unfortunately useless with JavaScript, and ChatGPT doesn't seem sufficiently versed in EventScripter to make the change for me, despite the very clear prompt I gave it. It keeps producing code using identifiers that are not part of EventScripter.
Reply With Quote
  #2  
Old 09.12.2024, 10:35
mgpai mgpai is offline
Script Master
 
Join Date: Sep 2013
Posts: 1,680
Default

Code:
/*
    move finished
    trigger : jdownloader started
*/

getAllFilePackages().forEach(function(package) {
    if (package.finished) {
        var curFolder = package.downloadFolder;
        var newFolder = curFolder.replace("\\downloading", "\\finished");

        if (curFolder != newFolder) {
            package.downloadFolder = newFolder;
        }
    }
})

Should work fine, but better run/try it on test install first.
Reply With Quote
  #3  
Old 10.12.2024, 04:48
rabidman rabidman is offline
DSL Light User
 
Join Date: Dec 2024
Location: San Jose, California
Posts: 33
Default

Hi,

Thanks for your answer. Response below.

Quote:
Originally Posted by mgpai View Post
Code:
/*
    move finished
    trigger : jdownloader started
*/

getAllFilePackages().forEach(function(package) {
    if (package.finished) {
        var curFolder = package.downloadFolder;
        var newFolder = curFolder.replace("\\downloading", "\\finished");

        if (curFolder != newFolder) {
            package.downloadFolder = newFolder;
        }
    }
})

Should work fine, but better run/try it on test install first.
Thanks for your response.
I tried it, as the following :

Code:
/*
    move finished
    trigger : jdownloader started
*/

getAllFilePackages().forEach(function(package) {
    if (package.finished) {
        var curFolder = package.downloadFolder;
        var newFolder = curFolder.replace("d:\\Downloads\\JDownloader", "d:\\Downloads\\Complete");

        if (curFolder != newFolder) {
            package.downloadFolder = newFolder;
        }
    }
})
Unfortunately, it had no effect, even after I restarted JDownloader. It seems to be a no-op. I have recently downloaded packages under D:\Downloads\JDownloader, each with their own directory structure, and nothing got moved to d:\Downloads\Complete .

Thanks again.

Last edited by rabidman; 10.12.2024 at 13:55.
Reply With Quote
  #4  
Old 10.12.2024, 09:19
mgpai mgpai is offline
Script Master
 
Join Date: Sep 2013
Posts: 1,680
Default

Only files which were downloaded by JD into that folder (and still exist there) will be moved. Files subsequently copied or extracted to that folder will not be moved.

This is by design, since multiple packages can share a (that) common download folder and it might contain files belonging to other packages.

I haver read your reply in the other thread. You can test/use this to to move the entire folder, keeping in mind it moves ALL the files/folders in it.

Code:
/*
    demo
    trigger : click test run in top panel
*/    

var myPackage = getPath("d:\\downloads\\jdownloader\\myPackage");
var complete = "d:\\downloads\\complete";
var moved = getPath(myPackage).moveTo(complete);

alert(moved);

If you prefer, you can also use CLI commands in eventscripter (using callSync/callAsync) to do the same.
Reply With Quote
  #5  
Old 10.12.2024, 10:17
rabidman rabidman is offline
DSL Light User
 
Join Date: Dec 2024
Location: San Jose, California
Posts: 33
Default

Hi,

Thanks again for your response. More inline.

Quote:
Originally Posted by mgpai View Post
Only files which were downloaded by JD into that folder (and still exist there) will be moved. Files subsequently copied or extracted to that folder will not be moved.
Why / how would files be subsequently copied, without manual intervention ?
And why would extraction cause an issue, as long as the extraction is performed inside the package folder ?

Quote:
This is by design, since multiple packages can share a (that) common download folder and it might contain files belonging to other packages.
Is there a way to prevent this, ie. creating unique folder names for each package ?
Or alternately, perform the move only after the last package with a shared folder name has completed downloading ?

Quote:
Quote:
I haver read your reply in the other thread. You can test/use this to to move the entire folder, keeping in mind it moves ALL the files/folders in it.

Code:
/*
    demo
    trigger : click test run in top panel
*/    

var myPackage = getPath("d:\\downloads\\jdownloader\\myPackage");
var complete = "d:\\downloads\\complete";
var moved = getPath(myPackage).moveTo(complete);

alert(moved);

If you prefer, you can also use CLI commands in eventscripter (using callSync/callAsync) to do the same.
Thank you ! That script worked. But of course, I had to hardcode the package name in there, which I don't want to do. How can I fix that ?

Also, what would be the correct trigger to use ?

Thanks again !

Last edited by rabidman; 10.12.2024 at 10:32.
Reply With Quote
  #6  
Old 10.12.2024, 12:35
mgpai mgpai is offline
Script Master
 
Join Date: Sep 2013
Posts: 1,680
Default

Quote:
Originally Posted by rabidman View Post
Why / how would files be subsequently copied, without manual intervention ?
And why would extraction cause an issue, as long as the extraction is performed inside the package folder ?
The script uses a package object method to 'set' the download folder. It will update the new path only to the links belonging to that package and moves the files correspondng to those links, to the new location. Thus, the connection between the download link and file on disk will be preserved. It will not be aware of any othe files in that folder, belonging to a different package or copied or extracted into it manually or otherwise, and hence they will not be moved.

Quote:
Originally Posted by rabidman View Post
Is there a way to prevent this, ie. creating unique folder names for each package ?
Or alternately, perform the move only after the last package with a shared folder
name has completed downloading ?
Extracted files would still not be moved.

Quote:
Originally Posted by rabidman View Post
Thank you ! That script worked. But of course, I had to hardcode the package name in there, which I don't want to do. How can I fix that ?

Also, what would be the correct trigger to use ?
That was just for you to check if it works as intended. "moveTo" can be used to replace "setFolder" method in the original script.

It would not be wise to 'set' the new path while extraction is active. So, the script uses 'jdownloader started' trigger, at which point extraction presumably will have been completed. A better option is to create a "toolbar button pressed" and manually run the script by clicking the button.

Point to note: "moveTo" is a filePath object method, when used to move files will sever all connection between the downloadPath and the file on disk. The link in the download list may no longer point to the correct location on disk

Recommedation:
  • Extract the files directly to the 'complete' folder. (Can be configured in Settings > Archive Extractor)
  • Use the first script in this thread, to move the downloaded files. (Any files in the folder belonging to other packages will continue to remain there.)
Reply With Quote
  #7  
Old 10.12.2024, 13:18
rabidman rabidman is offline
DSL Light User
 
Join Date: Dec 2024
Location: San Jose, California
Posts: 33
Default

Quote:
Originally Posted by Jiaz View Post
You manually move files somewhere else without JDownloader *being aware* of this. It looks at old location
for part files for extraction and other stuff. That's why the scripts should only move finished non archive files
or wait for extraction to finish and then move the extracted files.
OK. So, is there a better way to run the script only once extraction has completed, if any ?

Thanks.
Reply With Quote
  #8  
Old 10.12.2024, 13:20
Jiaz's Avatar
Jiaz Jiaz is offline
JD Manager
 
Join Date: Mar 2009
Location: Germany
Posts: 82,044
Default

@mgpai: see here for desired behaviour, https://board.jdownloader.org/showpo...22&postcount=5
__________________
JD-Dev & Server-Admin
Reply With Quote
  #9  
Old 10.12.2024, 15:06
mgpai mgpai is offline
Script Master
 
Join Date: Sep 2013
Posts: 1,680
Default

  • Do the packages contain only archives?
  • Do you want to keep the links in the downloadlist after they are downloads/extiracted/moved?
Reply With Quote
  #10  
Old 10.12.2024, 16:50
rabidman rabidman is offline
DSL Light User
 
Join Date: Dec 2024
Location: San Jose, California
Posts: 33
Default

Quote:
Originally Posted by mgpai View Post
  • Do the packages contain only archives?
  • Do you want to keep the links in the downloadlist after they are downloads/extiracted/moved?
No to both.
Reply With Quote
  #11  
Old 10.12.2024, 17:41
mgpai mgpai is offline
Script Master
 
Join Date: Sep 2013
Posts: 1,680
Default

Script:
Code:
/*
    change download folder
    trigger : interval (set interval in ms as desired, e.g. 3600000 for 1 hour)
*/

getAllFilePackages().forEach(function(package) {
    if (package.finished && !package.archives.length) {
        var curFolder = package.downloadFolder
        var newFolder = curFolder.replace("\\jdownloader\\", "\\complete\\");

        if (curFolder != newFolder) {
            package.downloadFolder = newFolder
            callAPI("downloadsV2", "removeLinks", [], [package.UUID]);
        }
    }
})

Archive extractor settings:


Workflow:
  • archive files will be extracted
  • if extracted file exists in the destination fodler it will be renamed
  • archive files will be removed
  • archive links will be removed from download list
  • script will run on interval and check if package does not contain any archives (wait till extraction process is finished)
  • new downloadFolder will be set (non-archive files will be moved)
  • package will be removed from downloadList

Please try on test install first.

Last edited by mgpai; 11.12.2024 at 08:16. Reason: Updated script
Reply With Quote
  #12  
Old 11.12.2024, 03:30
rabidman rabidman is offline
DSL Light User
 
Join Date: Dec 2024
Location: San Jose, California
Posts: 33
Default

Thank you ! This seems to be working, at least initially. I'll report if I find any issues.
Reply With Quote
  #13  
Old 10.12.2024, 16:26
pspzockerscene's Avatar
pspzockerscene pspzockerscene is offline
Community Manager
 
Join Date: Mar 2009
Location: Deutschland
Posts: 74,341
Default

Correct me if I'm wrong but at least for packages with only archives && when auto extract is enabled, you could use a Packagizer rule instead of a script or alternatively just define a custom global extraction path.
__________________
JD Supporter, Plugin Dev. & Community Manager

Erste Schritte & Tutorials || JDownloader 2 Setup Download
Spoiler:

A users' JD crashes and the first thing to ask is:
Quote:
Originally Posted by Jiaz View Post
Do you have Nero installed?
Reply With Quote
  #14  
Old 10.12.2024, 16:34
mgpai mgpai is offline
Script Master
 
Join Date: Sep 2013
Posts: 1,680
Default

Quote:
Originally Posted by pspzockerscene View Post
Correct me if I'm wrong but at least for packages with only archives && when auto extract is enabled, you could use a Packagizer rule instead of a script or ...
Yes, if the destination folder is static or can be generated with packagizer variables. Can also be used if the package contains non-arhives, but then only extracted files will be moved.

Quote:
Originally Posted by pspzockerscene View Post
...alternatively just define a custom global extraction path.
Best solution, suggested in one of the posts above.
Reply With Quote
  #15  
Old 10.12.2024, 16:41
pspzockerscene's Avatar
pspzockerscene pspzockerscene is offline
Community Manager
 
Join Date: Mar 2009
Location: Deutschland
Posts: 74,341
Default

Quote:
Originally Posted by mgpai View Post
Can also be used if the package contains non-arhives, but then only extracted files will be moved.
Yap.
This is a huge design flaw which a lot of users have asked us to properly implement.

It looks like the use case "move finished downloads to another place than the one they were initially downloaded to" is something that a lot of users want.
__________________
JD Supporter, Plugin Dev. & Community Manager

Erste Schritte & Tutorials || JDownloader 2 Setup Download
Spoiler:

A users' JD crashes and the first thing to ask is:
Quote:
Originally Posted by Jiaz View Post
Do you have Nero installed?
Reply With Quote
  #16  
Old 12.12.2024, 07:26
rabidman rabidman is offline
DSL Light User
 
Join Date: Dec 2024
Location: San Jose, California
Posts: 33
Default

Spoke too soon. Standalone files, ie. in the multiple virtual "Various files" packages, do not get moved.

Last edited by rabidman; 12.12.2024 at 10:44.
Reply With Quote
  #17  
Old 12.12.2024, 11:47
mgpai mgpai is offline
Script Master
 
Join Date: Sep 2013
Posts: 1,680
Default

Quote:
Originally Posted by rabidman View Post
Spoke too soon. Standalone files, ie. in the multiple virtual "Various files" packages, do not get moved.
Found out why?

Since the archive part is handled by the archive extractor, the script will have only to deal with non-archive files. Also, since you are using a pattern to save and move files, it is also possible to just move the non-archive files to the new location using a pattern.

Code:
/*
    move finished file
    trigger : download stopped
*/

if (link.finished && !link.archive) {
    var src = getPath(link.downloadPath);
    var packageName = link.package.name;
    var dest = getPath("d:/downloads/complete/" + packageName);
    var moved = src.moveTo(dest);

    if (moved) {
        link.remove();
        getPath(src.parent).delete();
    } else {
        alert(src + " was not moved.");
    }
}
Edit: added !link.archive check.

Last edited by mgpai; 12.12.2024 at 12:20.
Reply With Quote
  #18  
Old 13.12.2024, 15:07
rabidman rabidman is offline
DSL Light User
 
Join Date: Dec 2024
Location: San Jose, California
Posts: 33
Default

Quote:
Originally Posted by mgpai View Post
Found out why?

Since the archive part is handled by the archive extractor, the script will have only to deal with non-archive files. Also, since you are using a pattern to save and move files, it is also possible to just move the non-archive files to the new location using a pattern.

Code:
/*
    move finished file
    trigger : download stopped
*/

if (link.finished && !link.archive) {
    var src = getPath(link.downloadPath);
    var packageName = link.package.name;
    var dest = getPath("d:/downloads/complete/" + packageName);
    var moved = src.moveTo(dest);

    if (moved) {
        link.remove();
        getPath(src.parent).delete();
    } else {
        alert(src + " was not moved.");
    }
}
Edit: added !link.archive check.
Thanks ! It seems to be working well so far. I'm going to do more testing, but this is looking good.
Reply With Quote
  #19  
Old 13.12.2024, 15:14
mgpai mgpai is offline
Script Master
 
Join Date: Sep 2013
Posts: 1,680
Default

Quote:
Originally Posted by rabidman View Post
Thanks ! It seems to be working well so far. I'm going to do more testing, but this is looking good.
If necessary, additanol alerts when move fails can be added. e.g 'src file does not exist' and 'dest file exists".
Reply With Quote
  #20  
Old 19.12.2024, 18:59
rabidman rabidman is offline
DSL Light User
 
Join Date: Dec 2024
Location: San Jose, California
Posts: 33
Default

Quote:
Originally Posted by mgpai View Post
If necessary, additanol alerts when move fails can be added. e.g 'src file does not exist' and 'dest file exists".
I haven't seen the first case, but I believe I have seen the later. Sometimes, the same file exists in multiple packages of the same name. Thus, the move for the 2nd+ fails.

I would prefer fewer pop-ups, if possible. Ideally, comparing the files, and just deleting the source if it's identical to the destination, with an alert only if it's different, or the move fails.
Reply With Quote
  #21  
Old 20.12.2024, 11:08
pspzockerscene's Avatar
pspzockerscene pspzockerscene is offline
Community Manager
 
Join Date: Mar 2009
Location: Deutschland
Posts: 74,341
Default

Instead of popups you could also just write all possible alerts into one text-file.
If in the end, you got that error text-file left in your folder, you can look into it without unnerving alerts.
__________________
JD Supporter, Plugin Dev. & Community Manager

Erste Schritte & Tutorials || JDownloader 2 Setup Download
Spoiler:

A users' JD crashes and the first thing to ask is:
Quote:
Originally Posted by Jiaz View Post
Do you have Nero installed?
Reply With Quote
  #22  
Old 19.01.2025, 04:13
rabidman rabidman is offline
DSL Light User
 
Join Date: Dec 2024
Location: San Jose, California
Posts: 33
Default

I have gotten numerous problems with this script.

1. thousands upon thousands of files that did not get moved.
That is a lot of popups to dismiss. I had ChatGPT write an Autohotkey script to dismiss them. But this is not a solution. I'm not sure of the root cause, or how to solve the problem.

2. unwanted renaming of files and directories
I have noticed that files and directories that are downloaded have very different names in the target "Complete" directory. I don't understand why that is. The script should move files, but should not rename them, or rename the directory structure.

3. move is not atomic at the package level
I really want the package to be moved all at once when it is complete. I do not want to move one file at a time. This is so that I can manually move the folders to their final destination, without having to manually check in JDownloader whether there are still files pending download for that package. This is extremely tedious when there are dozens or even hundreds of folders, some of which can take days, weeks or even months to download due to hoster bandwidth limitations. Ie. there is never a time when my download list is empty, and I can confidently move anything out of Complete without any risk of race condition.

4. problems with DLC
I ran into a serious problem with JDownloader. It would no longer exit. It had to be killed from Windows Task manager every time. I saved my settings and my downloads into a DLC. When resuming downloads, the script got the following error stack :

net.sourceforge.htmlunit.corejs.javascript.EcmaError: TypeError: Cannot read property "name" from null (#8)
at net.sourceforge.htmlunit.corejs.javascript.ScriptRuntime.constructError(ScriptRuntime.java:3935)
at net.sourceforge.htmlunit.corejs.javascript.ScriptRuntime.constructError(ScriptRuntime.java:3919)
at net.sourceforge.htmlunit.corejs.javascript.ScriptRuntime.typeError(ScriptRuntime.java:3944)
at net.sourceforge.htmlunit.corejs.javascript.ScriptRuntime.typeError2(ScriptRuntime.java:3960)
at net.sourceforge.htmlunit.corejs.javascript.ScriptRuntime.undefReadError(ScriptRuntime.java:3971)
at net.sourceforge.htmlunit.corejs.javascript.ScriptRuntime.getObjectProp(ScriptRuntime.java:1519)
at net.sourceforge.htmlunit.corejs.javascript.Interpreter.interpretLoop(Interpreter.java:1243)
at script(:8)
at net.sourceforge.htmlunit.corejs.javascript.Interpreter.interpret(Interpreter.java:798)
at net.sourceforge.htmlunit.corejs.javascript.InterpretedFunction.call(InterpretedFunction.java:105)
at net.sourceforge.htmlunit.corejs.javascript.ContextFactory.doTopCall(ContextFactory.java:411)
at org.jdownloader.scripting.JSHtmlUnitPermissionRestricter$SandboxContextFactory.doTopCall(JSHtmlUnitP ermissionRestricter.java:134)
at net.sourceforge.htmlunit.corejs.javascript.ScriptRuntime.doTopCall(ScriptRuntime.java:3286)
at net.sourceforge.htmlunit.corejs.javascript.InterpretedFunction.exec(InterpretedFunction.java:115)
at net.sourceforge.htmlunit.corejs.javascript.Context.evaluateString(Context.java:1361)
at org.jdownloader.extensions.eventscripter.ScriptThread.evalUNtrusted(ScriptThread.java:346)
at org.jdownloader.extensions.eventscripter.ScriptThread.executeScipt(ScriptThread.java:194)
at org.jdownloader.extensions.eventscripter.ScriptThread.run(ScriptThread.java:174)

If I add new downloads with a URL, I don't get this error stack.

Last edited by rabidman; 19.01.2025 at 05:28.
Reply With Quote
  #23  
Old 20.01.2025, 03:07
Jiaz's Avatar
Jiaz Jiaz is offline
JD Manager
 
Join Date: Mar 2009
Location: Germany
Posts: 82,044
Default

@rabidman:
1.) instead of spending time to write autohotkey to auto dismiss the alerts, you should better spend time and find out why moving fails that often for you.
Are source and destination on same drive/location? maybe destination file already exists?
you should ask for help to modify script to 1.) retry and 2.) provide better logs on why it might have failed to move

2.) what script exactly are you using? the one shown in post 33 does not have any rename at all, so the different filename must be already in list before

3.) requires a total different sort of script as it has to wait for all links to reach sort of finished/final state and wait for any extractions to finish
Why not leave files at place and auto remove finished/downloaded files from list and setup a script that starts once the package is removed (no more links in it) and then start moving the files? That would be much easier to maintain as the package in JDownloader will become smaller and smaller while downloading and either its empty = finished = can move or some errors that you have to check first

4.) I've answered in your other thread.


Quote:
Originally Posted by rabidman View Post
net.sourceforge.htmlunit.corejs.javascript.EcmaError: TypeError: Cannot read property "name" from null (#8)
Quote:
var packageName = link.package.name;
link.package is only available while the link is in list. when you have enabled auto remove from list, then you have to enable synchronous execution of the script, else it may easily happen that at time of script execution, the entry is no longer in list and then the information is no longer available.
That would also explain the
Quote:
1. thousands upon thousands of files that did not get moved.
__________________
JD-Dev & Server-Admin

Last edited by Jiaz; 20.01.2025 at 03:10.
Reply With Quote
  #24  
Old 27.01.2025, 21:33
rabidman rabidman is offline
DSL Light User
 
Join Date: Dec 2024
Location: San Jose, California
Posts: 33
Default Cannot read property "name" from NULL pop-up

This has happened many times in the last week. I captured it in a log today :

27.01.25 11.19.40 <--> 27.01.25 11.33.08 jdlog://9858411370661/
__________________
Ph’nglui mglw’nafh Cthulhu R’lyeh wgah’nagl fhtagn.
Reply With Quote
  #25  
Old 29.01.2025, 12:38
Jiaz's Avatar
Jiaz Jiaz is offline
JD Manager
 
Join Date: Mar 2009
Location: Germany
Posts: 82,044
Default

@rabidman: You have a script in use in Eventscripter that does not correctly handle error.
Can you please tell us what script (link to script) or copy of the script you have active in your JDownloader, then we can check/help to fix it
__________________
JD-Dev & Server-Admin
Reply With Quote
  #26  
Old 29.01.2025, 13:18
pspzockerscene's Avatar
pspzockerscene pspzockerscene is offline
Community Manager
 
Join Date: Mar 2009
Location: Deutschland
Posts: 74,341
Default

Why a new thread?
I guess your post is still about the topic "Moving packages and files after completion"?
__________________
JD Supporter, Plugin Dev. & Community Manager

Erste Schritte & Tutorials || JDownloader 2 Setup Download
Spoiler:

A users' JD crashes and the first thing to ask is:
Quote:
Originally Posted by Jiaz View Post
Do you have Nero installed?
Reply With Quote
  #27  
Old 01.02.2025, 01:51
rabidman rabidman is offline
DSL Light User
 
Join Date: Dec 2024
Location: San Jose, California
Posts: 33
Default

Quote:
Originally Posted by pspzockerscene View Post
Why a new thread?
I guess your post is still about the topic **External links are only visible to Support Staff**...?
The script is indeed the one in that topic. However, I have it disabled in Eventscripter. Or at least I thought I did. The checkmark was unchecked in Eventscripter, and I thought that would disable it. But it appears that it still runs.

I guess I'm confused by the UI. Is there no way to disable a specific script while keeping Eventscripter enabled ?

For example, if I have 3 different scripts, can I just enable 1 and leave the other 2 disabled ? Or do I have to delete the scripts I'm not using ?
__________________
Ph’nglui mglw’nafh Cthulhu R’lyeh wgah’nagl fhtagn.
Reply With Quote
  #28  
Old 03.02.2025, 13:05
pspzockerscene's Avatar
pspzockerscene pspzockerscene is offline
Community Manager
 
Join Date: Mar 2009
Location: Deutschland
Posts: 74,341
Default

Quote:
Originally Posted by rabidman View Post
The script is indeed the one in that topic.
Merged same topic threads.

Quote:
Originally Posted by rabidman View Post
For example, if I have 3 different scripts, can I just enable 1 and leave the other 2 disabled ?
Yes.
Disabled scripts will not be executed anymore so yes, you can have multiple scripts and only enable the ones you really want to use.
__________________
JD Supporter, Plugin Dev. & Community Manager

Erste Schritte & Tutorials || JDownloader 2 Setup Download
Spoiler:

A users' JD crashes and the first thing to ask is:
Quote:
Originally Posted by Jiaz View Post
Do you have Nero installed?
Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

All times are GMT +2. The time now is 18:30.
Provided By AppWork GmbH | Privacy | Imprint
Parts of the Design are used from Kirsch designed by Andrew & Austin
Powered by vBulletin® Version 3.8.10 Beta 1
Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.