diff --git a/README.hbs b/README.hbs
new file mode 100644
index 0000000..65b6c34
--- /dev/null
+++ b/README.hbs
@@ -0,0 +1,247 @@
+# DSM-Window-Utilities
+
+Allows you to adjust or reset the size and position of windows on the DSM desktop for [Synology](https://www.synology.com) DiskStations.
+
+## Table of Contents
+
+1. [Introduction](#introduction)
+2. [Usage](#usage)
+ 1. [General](#general)
+ 2. [Using `cascadeOverlap([bounds])`](#using-cascadeoverlapbounds)
+ 3. [Using `resetRestoreSizeAndPosition([appNames])`](#using-resetrestoresizeandpositionappnames)
+ 4. [Using `launch([appNames])`](#using-launchappnames)
+ 5. [Using `setRestorePagePosition(appName, x, y)`](#using-setrestorepagepositionappname-x-y)
+3. [Compatibility](#compatibility)
+4. [References](#references)
+ 1. [Opening the Browser Console](#opening-the-browser-console)
+ 2. [List of supported Applications](#list-of-supported-applications)
+5. [API Reference](#api-reference)
+6. [License](#license)
+
+## Introduction
+
+By default, each new application window opened on the DSM desktop is offset horizontally and vertically 30px from the last window opened. This continues until part of the window would be outside the desktop area. In this case, the window is moved to the left and/or top of the desktop. Internally in the DSM, this algorithm is named **cascadeOverlap**.
+
+Whenever you move or resize an application window, a `restoreSizePos` entry is created in the current user profile that saves the current window size and location for that application. Newly opened windows then always get this size and position and no longer get their position through the **cascadeOverlap** algorithm.
+
+This default behavior has some drawbacks that are addressed by the DSM-Window-Utilities:
+
+* It is almost impossible to achieve a consistent window arrangement across different DiskStations by manually moving the windows.
+* If you frequently take screenshots of the DSM desktop and create a mask in Photoshop to crop individual windows, it's an extra effort to adjust the window position to the mask position each time.
+* In some cases, an application window may accidentally receive a `restoreSizePos` located outside the visible area of the desktop. This can only be fixed by manipulating the HTML of the page and changing the position of the element.
+
+DSM-Window-Utilities therefore provides a JavaScript API to adjust or reset the size and position of the application windows according to your requirements.
+
+## Usage
+
+### General
+
+1. Sign in to DSM with the user for whom you want to change the application windows.
+2. [Open](#opening-the-browser-console) the Browser's Developer Console.
+3. Copy the **contents** of the `bit-sds-windowutil.js` file, paste it into the console and press Enter. This creates the static class `BIT.SDS.WindowUtil` and allows you to call the provided [methods](#BIT.SDS.WindowUtil).
+4. Now you can simply call a method by entering e.g. `BIT.SDS.WindowUtil.launch();`. This example launches all installed [applications](#list-of-supported-applications) that display a window on the DSM desktop.
+
+### Using `cascadeOverlap([bounds])`
+
+Probably the most useful method is `cascadeOverlap()`. This method arranges all application windows with the same algorithm as the internal **cascadeOverlap**, but assigns a fixed position to the windows by setting the `restoreSizePos`. The position of the individual application windows is determined only by the limits specified by the optional `bounds` argument. As long as you call this method with the same `bounds`, each window has the same individual position, regardless of which applications you have installed, which DSM version you are using, or how large your browser window is.
+
+Unless you want to customize the `bounds`, you don't need to specify the bounds values. You can simply rely on the [suggestion](#BIT.SDS.WindowUtil.suggestBounds) made when calling `cascadeOverlap()` without argument.
+
+Make sure that you first undock the console window from the browser window and set the browser window to the desired size. Then type into the console:
+
+```js
+BIT.SDS.WindowUtil.cascadeOverlap();
+```
+
+You will notice that `cascadeOverlap()` logs the bounds it uses in the console. For example:
+
+```
+Using: BIT.SDS.WindowUtil.cascadeOverlap({x: 160, y: 139, width: 1640, height: 830});
+```
+
+To see the effect of the new window arrangement, you can use the following command, which first closes all windows and then opens them again:
+
+```js
+BIT.SDS.WindowUtil.close()
+ .then(BIT.SDS.WindowUtil.launch());
+```
+
+If you want to customize the bounds, start changing the values of `x`, `y`, `width` or `height` to your liking and call `cascadeOverlap()` again. For example:
+
+```js
+BIT.SDS.WindowUtil.cascadeOverlap({x: 0, y: 39, width: 1920, height: 1161});
+```
+
+**Note:** Once you have found **your** `bounds`, note them down so that you can reproduce the call at any time.
+
+### Using `resetRestoreSizeAndPosition([appNames])`
+
+If you like the default behavior of DSM, which moves each newly opened window, but you have already moved or resized some windows, you can simply restore the default with the following command, which removes the `restoreSizePos` from each application window, so that you get a fresh start:
+
+```js
+BIT.SDS.WindowUtil.resetRestoreSizeAndPosition();
+```
+
+For a more specific reset, you can also specify a single `appName` or an array of `appName`s as in these examples:
+
+```js
+BIT.SDS.WindowUtil.resetRestoreSizeAndPosition("SYNO.SDS.App.FileStation3.Instance");
+```
+
+```js
+BIT.SDS.WindowUtil.resetRestoreSizeAndPosition(["SYNO.SDS.App.FileStation3.Instance", "SYNO.SDS.PkgManApp.Instance"]);
+```
+
+See [here](#list-of-supported-applications) for a list of supported applications and their respective `appName`.
+
+### Using `launch([appNames])`
+
+The `launch()` method can be used to launch applications that are not running and open the application window. This allows you to easily get an overview of how your application windows are currently arranged:
+
+```js
+BIT.SDS.WindowUtil.launch();
+```
+
+The `launch()` method returns a promise that is fulfilled with an array of all launched application instances. This allows you to call methods of the application instance class (`SYNO.SDS.AppInstance`). A simple example is setting the position of the application instance window:
+
+```js
+BIT.SDS.WindowUtil.launch("SYNO.SDS.App.FileStation3.Instance")
+ .then(function(results) {
+ results[0].window.setPagePosition(0, 39);
+ });
+```
+
+Internally, the promise returned by `launch()` is used by the `getDefaultSize()` method, which retrieves the window size from the application instance window.
+
+**Note:** You do not have to start an application just to get your application instance. If the application is already running, you can use the method `SYNO.SDS.AppMgr.getByAppName(appName)` to get an array of all instances of a particular application.
+
+### Using `setRestorePagePosition(appName, x, y)`
+
+If you want to permanently set the position of an application window, you can use `setRestorePagePosition()` which writes the provided x,y page position to the `restoreSizePos`. In this example, the position of the File Station application is set to the top left corner of the DSM desktop:
+
+```js
+BIT.SDS.WindowUtil.setRestorePagePosition("SYNO.SDS.App.FileStation3.Instance", 0, 39);
+```
+
+## Compatibility
+
+DSM-Window-Utilities has been tested on the following DSM versions:
+
+* **DSM 5.2**
+* **DSM 6.0.x**
+* **DSM 6.1.x**
+* **DSM 6.2.x**
+
+## References
+
+### Opening the Browser Console
+
+| Browser | Mac | Windows / Linux |
+| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------- |
+| Chrome | Cmd+Option+J | Ctrl+Shift+J |
+| Firefox | Cmd+Option+K | Ctrl+Shift+K |
+| Safari | Cmd+Option+C
**Note**: You need to enable
*Show Developer menu in menu bar*
in *Preferences/Advanced* before. | N/A |
+| Opera | Cmd+Option+J | Ctrl+Shift+J |
+| Edge | N/A | F12 and click on *Console* tab |
+| Internet Explorer | N/A | F12 and click on *Console* tab |
+
+### List of supported Applications
+
+| Application | appName |
+| ---------------------------------------------- | ----------------------------------------- |
+| Control Panel | `SYNO.SDS.AdminCenter.Application` |
+| File Station | `SYNO.SDS.App.FileStation3.Instance` |
+| EZ-Internet | `SYNO.SDS.EzInternet.Instance` |
+| DSM Help | `SYNO.SDS.HelpBrowser.Application` |
+| Package Center | `SYNO.SDS.PkgManApp.Instance` |
+| Resource Monitor | `SYNO.SDS.ResourceMonitor.Instance` |
+| Storage Manager | `SYNO.SDS.StorageManager.Instance` |
+| High Availability Manager | `SYNO.SDS.HA.Instance` |
+| Log Center | `SYNO.SDS.LogCenter.Instance` |
+| Security Advisor | `SYNO.SDS.SecurityScan.Instance` |
+| Support Center | `SYNO.SDS.SupportForm.Application` |
+| Options / Personal | `SYNO.SDS.App.PersonalSettings.Instance` |
+| Backup & Replication | `SYNO.SDS.Backup.Application` |
+| Text Editor | `SYNO.SDS.ACEEditor.Application` |
+| Synology Account | `SYNO.SDS.MyDSCenter.Application` |
+| Storage Analyzer | `SYNO.SDS.StorageReport.Application` |
+| Antivirus Essential & Antivirus by McAfee | `SYNO.SDS.AV.Instance` |
+| Audio Station | `SYNO.SDS.AudioStation.Application` |
+| CardDAV Server | `SYNO.SDS.CardDAVServer.Instance` |
+| Cloud Station / Cloud Station Server | `SYNO.SDS.CSTN.Instance` |
+| Cloud Station Client / Cloud Station ShareSync | `SYNO.SDS.CloudStationClient.Instance` |
+| Cloud Sync | `SYNO.SDS.DSCloudSync.Instance` |
+| CMS | `SYNO.SDS.CMS.Application` |
+| Directory Server | `SYNO.SDS.LDAP.AppInstance` |
+| DNS Server | `SYNO.SDS.DNS.Instance` |
+| Docker | `SYNO.SDS.Docker.Application` |
+| Download Station | `SYNO.SDS.DownloadStation.Application` |
+| Git Server | `SYNO.SDS.GIT.Instance` |
+| Glacier Backup | `SYNO.SDS.Glacier.Instance` |
+| HiDrive Backup | `SYNO.SDS.HiDrive.Instance` |
+| iTunes Server | `SYNO.SDS.iTunes.Application` |
+| Java Manager | `SYNO.SDS.JAVAMANAGER.Instance` |
+| Mail Server | `SYNO.SDS.MailServer.Instance` |
+| MariaDB / MariaDB 5 | `SYNO.SDS.MARIADB.Instance` |
+| Media Server | `SYNO.SDS.MediaServer.AppInstance` |
+| Note Station | `SYNO.SDS.NoteStation.Application` |
+| Proxy Server | `SYNO.SDS.ProxyServer.Instance` |
+| RADIUS Server | `SYNO.SDS.RAD.Instance` |
+| SSO Server | `SYNO.SDS.SSOServer.Instance` |
+| SVN | `SYNO.SDS.SVN.Instance` |
+| Video Station | `SYNO.SDS.VideoStation.AppInstance` |
+| VPN Server | `SYNO.SDS.VPN.Instance` |
+| Active Backup for Server | `SYNO.SDS.ActiveBackup.Instance` |
+| Hyper Backup | `SYNO.SDS.Backup.Application` |
+| Hyper Backup Vault | `SYNO.SDS.BackupService.Instance` |
+| Java7 | `SYNO.SDS.JAVA7.Instance` |
+| Java8 | `SYNO.SDS.JAVA8.Instance` |
+| MailPlus Server | `SYNO.SDS.MailPlusServer.Instance` |
+| MariaDB 10 | `SYNO.SDS.MARIADB10.Instance` |
+| Notification Settings | `SYNO.SDS.DSMNotify.Setting.Application` |
+| PetaSpace | `SYNO.SDS.ClusteredShare.Application` |
+| Snapshot Replication | `SYNO.SDS.DisasterRecovery.Application` |
+| Storage Analyzer | `SYNO.SDS.StorageAnalyzer.Application` |
+| Text Editor | `SYNO.TextEditor.Application` |
+| Web Station | `SYNO.SDS.WebStation.Application` |
+| WebDAV Server | `SYNO.SDS.WebDAVServer.Instance` |
+| Universal Search | `SYNO.Finder.Application` |
+| Active Backup for G Suite | `SYNO.SDS.ActiveBackupGSuite.Instance` |
+| Active Backup for Office 365 | `SYNO.SDS.ActiveBackupOffice365.Instance` |
+| Presto File Server | `SYNO.SDS.PrestoServer.Application` |
+| USB Copy | `SYNO.SDS.USBCopy.Application` |
+| Active Backup for Business | `SYNO.ActiveBackup.AppInstance` |
+| iSCSI Manager | `SYNO.SDS.iSCSI.Application` |
+| OAuth Service | `SYNO.SDS.OAuthService.Instance` |
+| Active Directory Server | `SYNO.SDS.ADServer.Application` |
+| Virtual Machine Manager | `SYNO.SDS.Virtualization.Application` |
+
+## API Reference
+
+{{#namespace name="BIT"}}
+{{>header~}}
+{{>body}}
+{{/namespace}}
+{{>separator~}}
+
+{{#namespace name="SDS"}}
+{{>header~}}
+{{>body}}
+{{/namespace}}
+{{>separator~}}
+
+{{#class name="WindowUtil"}}
+{{>docs~}}
+{{/class}}
+{{>separator~}}
+
+{{#class name="Promise"}}
+{{>docs~}}
+{{/class}}
+{{>separator~}}
+
+## License
+
+Copyright (C) 2019 Michael Berger (BITespresso)
+
+DSM-Window-Utilities is licensed under the **GNU General Public License v3 (GPL-3)** (http://www.gnu.org/copyleft/gpl.html).
diff --git a/README.md b/README.md
index 9d01030..c88ccec 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,799 @@
-# DSM-Window-Utilities
\ No newline at end of file
+# DSM-Window-Utilities
+
+Allows you to adjust or reset the size and position of windows on the DSM desktop for [Synology](https://www.synology.com) DiskStations.
+
+## Table of Contents
+
+1. [Introduction](#user-content-introduction)
+2. [Usage](#user-content-usage)
+ 1. [General](#user-content-general)
+ 2. [Using `cascadeOverlap([bounds])`](#user-content-using-cascadeoverlapbounds)
+ 3. [Using `resetRestoreSizeAndPosition([appNames])`](#user-content-using-resetrestoresizeandpositionappnames)
+ 4. [Using `launch([appNames])`](#user-content-using-launchappnames)
+ 5. [Using `setRestorePagePosition(appName, x, y)`](#user-content-using-setrestorepagepositionappname-x-y)
+3. [Compatibility](#user-content-compatibility)
+4. [References](#user-content-references)
+ 1. [Opening the Browser Console](#user-content-opening-the-browser-console)
+ 2. [List of supported Applications](#user-content-list-of-supported-applications)
+5. [API Reference](#user-content-api-reference)
+6. [License](#user-content-license)
+
+## Introduction
+
+By default, each new application window opened on the DSM desktop is offset horizontally and vertically 30px from the last window opened. This continues until part of the window would be outside the desktop area. In this case, the window is moved to the left and/or top of the desktop. Internally in the DSM, this algorithm is named **cascadeOverlap**.
+
+Whenever you move or resize an application window, a `restoreSizePos` entry is created in the current user profile that saves the current window size and location for that application. Newly opened windows then always get this size and position and no longer get their position through the **cascadeOverlap** algorithm.
+
+This default behavior has some drawbacks that are addressed by the DSM-Window-Utilities:
+
+* It is almost impossible to achieve a consistent window arrangement across different DiskStations by manually moving the windows.
+* If you frequently take screenshots of the DSM desktop and create a mask in Photoshop to crop individual windows, it's an extra effort to adjust the window position to the mask position each time.
+* In some cases, an application window may accidentally receive a `restoreSizePos` located outside the visible area of the desktop. This can only be fixed by manipulating the HTML of the page and changing the position of the element.
+
+DSM-Window-Utilities therefore provides a JavaScript API to adjust or reset the size and position of the application windows according to your requirements.
+
+## Usage
+
+### General
+
+1. Sign in to DSM with the user for whom you want to change the application windows.
+2. [Open](#user-content-opening-the-browser-console) the Browser's Developer Console.
+3. Copy the **contents** of the `bit-sds-windowutil.js` file, paste it into the console and press Enter. This creates the static class `BIT.SDS.WindowUtil` and allows you to call the provided [methods](#user-content-bit.sds.windowutil).
+4. Now you can simply call a method by entering e.g. `BIT.SDS.WindowUtil.launch();`. This example launches all installed [applications](#user-content-list-of-supported-applications) that display a window on the DSM desktop.
+
+### Using `cascadeOverlap([bounds])`
+
+Probably the most useful method is `cascadeOverlap()`. This method arranges all application windows with the same algorithm as the internal **cascadeOverlap**, but assigns a fixed position to the windows by setting the `restoreSizePos`. The position of the individual application windows is determined only by the limits specified by the optional `bounds` argument. As long as you call this method with the same `bounds`, each window has the same individual position, regardless of which applications you have installed, which DSM version you are using, or how large your browser window is.
+
+Unless you want to customize the `bounds`, you don't need to specify the bounds values. You can simply rely on the [suggestion](#user-content-bit.sds.windowutil.suggestbounds) made when calling `cascadeOverlap()` without argument.
+
+Make sure that you first undock the console window from the browser window and set the browser window to the desired size. Then type into the console:
+
+```js
+BIT.SDS.WindowUtil.cascadeOverlap();
+```
+
+You will notice that `cascadeOverlap()` logs the bounds it uses in the console. For example:
+
+```
+Using: BIT.SDS.WindowUtil.cascadeOverlap({x: 160, y: 139, width: 1640, height: 830});
+```
+
+To see the effect of the new window arrangement, you can use the following command, which first closes all windows and then opens them again:
+
+```js
+BIT.SDS.WindowUtil.close()
+ .then(BIT.SDS.WindowUtil.launch());
+```
+
+If you want to customize the bounds, start changing the values of `x`, `y`, `width` or `height` to your liking and call `cascadeOverlap()` again. For example:
+
+```js
+BIT.SDS.WindowUtil.cascadeOverlap({x: 0, y: 39, width: 1920, height: 1161});
+```
+
+**Note:** Once you have found **your** `bounds`, note them down so that you can reproduce the call at any time.
+
+### Using `resetRestoreSizeAndPosition([appNames])`
+
+If you like the default behavior of DSM, which moves each newly opened window, but you have already moved or resized some windows, you can simply restore the default with the following command, which removes the `restoreSizePos` from each application window, so that you get a fresh start:
+
+```js
+BIT.SDS.WindowUtil.resetRestoreSizeAndPosition();
+```
+
+For a more specific reset, you can also specify a single `appName` or an array of `appName`s as in these examples:
+
+```js
+BIT.SDS.WindowUtil.resetRestoreSizeAndPosition("SYNO.SDS.App.FileStation3.Instance");
+```
+
+```js
+BIT.SDS.WindowUtil.resetRestoreSizeAndPosition(["SYNO.SDS.App.FileStation3.Instance", "SYNO.SDS.PkgManApp.Instance"]);
+```
+
+See [here](#user-content-list-of-supported-applications) for a list of supported applications and their respective `appName`.
+
+### Using `launch([appNames])`
+
+The `launch()` method can be used to launch applications that are not running and open the application window. This allows you to easily get an overview of how your application windows are currently arranged:
+
+```js
+BIT.SDS.WindowUtil.launch();
+```
+
+The `launch()` method returns a promise that is fulfilled with an array of all launched application instances. This allows you to call methods of the application instance class (`SYNO.SDS.AppInstance`). A simple example is setting the position of the application instance window:
+
+```js
+BIT.SDS.WindowUtil.launch("SYNO.SDS.App.FileStation3.Instance")
+ .then(function(results) {
+ results[0].window.setPagePosition(0, 39);
+ });
+```
+
+Internally, the promise returned by `launch()` is used by the `getDefaultSize()` method, which retrieves the window size from the application instance window.
+
+**Note:** You do not have to start an application just to get your application instance. If the application is already running, you can use the method `SYNO.SDS.AppMgr.getByAppName(appName)` to get an array of all instances of a particular application.
+
+### Using `setRestorePagePosition(appName, x, y)`
+
+If you want to permanently set the position of an application window, you can use `setRestorePagePosition()` which writes the provided x,y page position to the `restoreSizePos`. In this example, the position of the File Station application is set to the top left corner of the DSM desktop:
+
+```js
+BIT.SDS.WindowUtil.setRestorePagePosition("SYNO.SDS.App.FileStation3.Instance", 0, 39);
+```
+
+## Compatibility
+
+DSM-Window-Utilities has been tested on the following DSM versions:
+
+* **DSM 5.2**
+* **DSM 6.0.x**
+* **DSM 6.1.x**
+* **DSM 6.2.x**
+
+## References
+
+### Opening the Browser Console
+
+| Browser | Mac | Windows / Linux |
+| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------- |
+| Chrome | Cmd+Option+J | Ctrl+Shift+J |
+| Firefox | Cmd+Option+K | Ctrl+Shift+K |
+| Safari | Cmd+Option+C
**Note**: You need to enable
*Show Developer menu in menu bar*
in *Preferences/Advanced* before. | N/A |
+| Opera | Cmd+Option+J | Ctrl+Shift+J |
+| Edge | N/A | F12 and click on *Console* tab |
+| Internet Explorer | N/A | F12 and click on *Console* tab |
+
+### List of supported Applications
+
+| Application | appName |
+| ---------------------------------------------- | ----------------------------------------- |
+| Control Panel | `SYNO.SDS.AdminCenter.Application` |
+| File Station | `SYNO.SDS.App.FileStation3.Instance` |
+| EZ-Internet | `SYNO.SDS.EzInternet.Instance` |
+| DSM Help | `SYNO.SDS.HelpBrowser.Application` |
+| Package Center | `SYNO.SDS.PkgManApp.Instance` |
+| Resource Monitor | `SYNO.SDS.ResourceMonitor.Instance` |
+| Storage Manager | `SYNO.SDS.StorageManager.Instance` |
+| High Availability Manager | `SYNO.SDS.HA.Instance` |
+| Log Center | `SYNO.SDS.LogCenter.Instance` |
+| Security Advisor | `SYNO.SDS.SecurityScan.Instance` |
+| Support Center | `SYNO.SDS.SupportForm.Application` |
+| Options / Personal | `SYNO.SDS.App.PersonalSettings.Instance` |
+| Backup & Replication | `SYNO.SDS.Backup.Application` |
+| Text Editor | `SYNO.SDS.ACEEditor.Application` |
+| Synology Account | `SYNO.SDS.MyDSCenter.Application` |
+| Storage Analyzer | `SYNO.SDS.StorageReport.Application` |
+| Antivirus Essential & Antivirus by McAfee | `SYNO.SDS.AV.Instance` |
+| Audio Station | `SYNO.SDS.AudioStation.Application` |
+| CardDAV Server | `SYNO.SDS.CardDAVServer.Instance` |
+| Cloud Station / Cloud Station Server | `SYNO.SDS.CSTN.Instance` |
+| Cloud Station Client / Cloud Station ShareSync | `SYNO.SDS.CloudStationClient.Instance` |
+| Cloud Sync | `SYNO.SDS.DSCloudSync.Instance` |
+| CMS | `SYNO.SDS.CMS.Application` |
+| Directory Server | `SYNO.SDS.LDAP.AppInstance` |
+| DNS Server | `SYNO.SDS.DNS.Instance` |
+| Docker | `SYNO.SDS.Docker.Application` |
+| Download Station | `SYNO.SDS.DownloadStation.Application` |
+| Git Server | `SYNO.SDS.GIT.Instance` |
+| Glacier Backup | `SYNO.SDS.Glacier.Instance` |
+| HiDrive Backup | `SYNO.SDS.HiDrive.Instance` |
+| iTunes Server | `SYNO.SDS.iTunes.Application` |
+| Java Manager | `SYNO.SDS.JAVAMANAGER.Instance` |
+| Mail Server | `SYNO.SDS.MailServer.Instance` |
+| MariaDB / MariaDB 5 | `SYNO.SDS.MARIADB.Instance` |
+| Media Server | `SYNO.SDS.MediaServer.AppInstance` |
+| Note Station | `SYNO.SDS.NoteStation.Application` |
+| Proxy Server | `SYNO.SDS.ProxyServer.Instance` |
+| RADIUS Server | `SYNO.SDS.RAD.Instance` |
+| SSO Server | `SYNO.SDS.SSOServer.Instance` |
+| SVN | `SYNO.SDS.SVN.Instance` |
+| Video Station | `SYNO.SDS.VideoStation.AppInstance` |
+| VPN Server | `SYNO.SDS.VPN.Instance` |
+| Active Backup for Server | `SYNO.SDS.ActiveBackup.Instance` |
+| Hyper Backup | `SYNO.SDS.Backup.Application` |
+| Hyper Backup Vault | `SYNO.SDS.BackupService.Instance` |
+| Java7 | `SYNO.SDS.JAVA7.Instance` |
+| Java8 | `SYNO.SDS.JAVA8.Instance` |
+| MailPlus Server | `SYNO.SDS.MailPlusServer.Instance` |
+| MariaDB 10 | `SYNO.SDS.MARIADB10.Instance` |
+| Notification Settings | `SYNO.SDS.DSMNotify.Setting.Application` |
+| PetaSpace | `SYNO.SDS.ClusteredShare.Application` |
+| Snapshot Replication | `SYNO.SDS.DisasterRecovery.Application` |
+| Storage Analyzer | `SYNO.SDS.StorageAnalyzer.Application` |
+| Text Editor | `SYNO.TextEditor.Application` |
+| Web Station | `SYNO.SDS.WebStation.Application` |
+| WebDAV Server | `SYNO.SDS.WebDAVServer.Instance` |
+| Universal Search | `SYNO.Finder.Application` |
+| Active Backup for G Suite | `SYNO.SDS.ActiveBackupGSuite.Instance` |
+| Active Backup for Office 365 | `SYNO.SDS.ActiveBackupOffice365.Instance` |
+| Presto File Server | `SYNO.SDS.PrestoServer.Application` |
+| USB Copy | `SYNO.SDS.USBCopy.Application` |
+| Active Backup for Business | `SYNO.ActiveBackup.AppInstance` |
+| iSCSI Manager | `SYNO.SDS.iSCSI.Application` |
+| OAuth Service | `SYNO.SDS.OAuthService.Instance` |
+| Active Directory Server | `SYNO.SDS.ADServer.Application` |
+| Virtual Machine Manager | `SYNO.SDS.Virtualization.Application` |
+
+## API Reference
+
+
+
+### BIT : object
+BITespresso namespace.
+
+**Kind**: global namespace
+
+
+### BIT.SDS : object
+Synology DiskStation namespace.
+
+**Kind**: static namespace of [BIT
](#user-content-bit)
+
+
+### SDS.WindowUtil
+**Kind**: static class of [SDS
](#user-content-bit.sds)
+
+* [.WindowUtil](#user-content-bit.sds.windowutil)
+ * _static_
+ * [.cascadeOverlap([bounds])](#user-content-bit.sds.windowutil.cascadeoverlap)
+ * [.close([appNames])](#user-content-bit.sds.windowutil.close) ⇒ [Promise
](#user-content-bit.sds.promise)
+ * [.getAppData()](#user-content-bit.sds.windowutil.getappdata) ⇒ [Array.<AppData>
](#user-content-bit.sds.windowutil..appdata)
+ * [.getAppNames()](#user-content-bit.sds.windowutil.getappnames) ⇒ Array.<string>
+ * [.getAppNamesForDsmVersion()](#user-content-bit.sds.windowutil.getappnamesfordsmversion) ⇒ Array.<string>
+ * [.getDefaultSize([appNames])](#user-content-bit.sds.windowutil.getdefaultsize) ⇒ [Promise
](#user-content-bit.sds.promise)
+ * [.getDsmVersion()](#user-content-bit.sds.windowutil.getdsmversion) ⇒ string
+ * [.getRestoreSizePosPropertyName(appName)](#user-content-bit.sds.windowutil.getrestoresizepospropertyname) ⇒ string
+ * [.hasRunningInstance(appName)](#user-content-bit.sds.windowutil.hasrunninginstance) ⇒ boolean
+ * [.isAppNameForDsmVersion(appName)](#user-content-bit.sds.windowutil.isappnamefordsmversion) ⇒ boolean
+ * [.isInstalled(appName)](#user-content-bit.sds.windowutil.isinstalled) ⇒ boolean
+ * [.launch([appNames])](#user-content-bit.sds.windowutil.launch) ⇒ [Promise
](#user-content-bit.sds.promise)
+ * [.logDefaultSize([appNames])](#user-content-bit.sds.windowutil.logdefaultsize)
+ * [.resetRestoreSizeAndPosition([appNames])](#user-content-bit.sds.windowutil.resetrestoresizeandposition)
+ * [.setRestorePagePosition(appName, x, y)](#user-content-bit.sds.windowutil.setrestorepageposition)
+ * [.suggestBounds()](#user-content-bit.sds.windowutil.suggestbounds) ⇒ [Bounds
](#user-content-bit.sds.windowutil..bounds)
+ * _inner_
+ * [~AppData](#user-content-bit.sds.windowutil..appdata) : Object
+ * [~AppWinSize](#user-content-bit.sds.windowutil..appwinsize) : Object
+ * [~Bounds](#user-content-bit.sds.windowutil..bounds) : Object
+
+
+
+### WindowUtil.cascadeOverlap([bounds])
+Sets the restore XY position of all applications to cascaded, overlapping positions
+within the specified bounds and resets the restore size so that the windows will have
+their respective default sizes.
+
+The algorithm used ensures that each window has a position that depends entirely on the
+specified bounds, regardless of which applications are installed or which DSM version is
+used.
+
+The method call including the parameters used is logged in the console. Please note this
+down for your later information.
+
+**Note 1**: Currently open application windows will not change their size and position.
+You must close and reopen the application window to see the result. Do not move or resize
+the application window beforehand, as this immediately sets the restore size and position
+to the current window size and position.
+
+**Note 2**: The Synology CMS (Central Management System) application
+(`SYNO.SDS.CMS.Application`) does not read the restore size and position due to a bug in
+DSM. To ensure that this window has the correct size and position, each time this method
+is called, the application will be launched and the window will be set to the correct
+size and position.
+
+**Kind**: static method of [WindowUtil
](#user-content-bit.sds.windowutil)
+
+| Param | Type | Description |
+| --- | --- | --- |
+| [bounds] | [Bounds
](#user-content-bit.sds.windowutil..bounds) | The bounds. |
+
+**Example**
+```js
+BIT.SDS.WindowUtil.cascadeOverlap();
+```
+**Example**
+```js
+BIT.SDS.WindowUtil.cascadeOverlap({x: 160, y: 139, width: 1640, height: 830});
+```
+
+
+### WindowUtil.close([appNames]) ⇒ [Promise
](#user-content-bit.sds.promise)
+Closes the provided or all application(s).
+
+Closing application(s) is an asychronous operation, therefore this method returns a
+promise that is fulfilled when all provided applications are closed.
+
+If you call this method without providing `appNames`, all applications that can open a
+window on the DSM desktop and are currently installed on the DiskStation will be closed.
+
+**Kind**: static method of [WindowUtil
](#user-content-bit.sds.windowutil)
+**Returns**: [Promise
](#user-content-bit.sds.promise) - A promise.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| [appNames] | Array.<string>
\| string
| The application name(s). |
+
+
+
+### WindowUtil.getAppData() ⇒ [Array.<AppData>
](#user-content-bit.sds.windowutil..appdata)
+Returns an array of [AppData](#user-content-bit.sds.windowutil..appdata) for all supported applications.
+
+**Kind**: static method of [WindowUtil
](#user-content-bit.sds.windowutil)
+**Returns**: [Array.<AppData>
](#user-content-bit.sds.windowutil..appdata) - An array of `AppData` objects.
+
+
+### WindowUtil.getAppNames() ⇒ Array.<string>
+Returns an array of all applications that can open a window on the DSM desktop.
+
+**Kind**: static method of [WindowUtil
](#user-content-bit.sds.windowutil)
+**Returns**: Array.<string>
- An array of application names.
+**Example**
+```js
+BIT.SDS.WindowUtil.getAppNames();
+// => ["SYNO.SDS.AdminCenter.Application", ...]
+```
+
+
+### WindowUtil.getAppNamesForDsmVersion() ⇒ Array.<string>
+Returns an array of all applications that can open a window on the DSM desktop and are
+available for the DSM version currently installed on the DiskStation.
+
+**Kind**: static method of [WindowUtil
](#user-content-bit.sds.windowutil)
+**Returns**: Array.<string>
- An array of application names.
+**Example**
+```js
+BIT.SDS.WindowUtil.getAppNamesForDsmVersion();
+// => ["SYNO.SDS.AdminCenter.Application", ...]
+```
+
+
+### WindowUtil.getDefaultSize([appNames]) ⇒ [Promise
](#user-content-bit.sds.promise)
+Retrieves the respective default window size(s) of the provided or all application(s).
+
+To get the default window size, first the restore size and XY position are reset via
+[resetRestoreSizeAndPosition](#user-content-bit.sds.windowutil.resetrestoresizeandposition), next the application is launched
+and finally the size of the newly opened application window is retrieved.
+
+Therefore please note:
+
+- The default window size can only be retrieved for currently installed applications.
+- The applications must not be running when calling this method.
+- The current restore size and XY position will be reset for those applications.
+
+Launching the application(s) is an asychronous operation, therefore this method returns a
+promise that is fulfilled with an array of [AppWinSize](#user-content-bit.sds.windowutil..appwinsize) objects.
+
+If you call this method without providing `appNames`, the default window sizes of all
+applications that can open a window on the DSM desktop and are currently installed on the
+DiskStation will be retrieved.
+
+**Kind**: static method of [WindowUtil
](#user-content-bit.sds.windowutil)
+**Returns**: [Promise
](#user-content-bit.sds.promise) - A promise for an array of `AppWinSize` objects.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| [appNames] | Array.<string>
\| string
| The application name(s). |
+
+
+
+### WindowUtil.getDsmVersion() ⇒ string
+Returns the DSM version currently installed on the DiskStation. The version has the
+format: `.`
+
+**Kind**: static method of [WindowUtil
](#user-content-bit.sds.windowutil)
+**Returns**: string
- The DSM version.
+**Example**
+```js
+BIT.SDS.WindowUtil.getDsmVersion();
+// => 6.2
+```
+
+
+### WindowUtil.getRestoreSizePosPropertyName(appName) ⇒ string
+Returns the name of the property that holds the restore size and position of the
+application window.
+
+**Kind**: static method of [WindowUtil
](#user-content-bit.sds.windowutil)
+**Returns**: string
- The property name.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| appName | string
| The application name. |
+
+
+
+### WindowUtil.hasRunningInstance(appName) ⇒ boolean
+Returns `true` if the provided application has at least one running instance.
+
+**Kind**: static method of [WindowUtil
](#user-content-bit.sds.windowutil)
+**Returns**: boolean
- `true` if has running instance, `false` otherwise.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| appName | string
| The application name. |
+
+
+
+### WindowUtil.isAppNameForDsmVersion(appName) ⇒ boolean
+Returns `true` if the provided application name is an application that can open a window
+on the DSM desktop and is available for the DSM version currently installed on the
+DiskStation.
+
+**Kind**: static method of [WindowUtil
](#user-content-bit.sds.windowutil)
+**Returns**: boolean
- `true` if valid, `false` otherwise.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| appName | string
| The application name. |
+
+
+
+### WindowUtil.isInstalled(appName) ⇒ boolean
+Returns `true` if the provided application is currently installed on the DiskStation.
+
+**Kind**: static method of [WindowUtil
](#user-content-bit.sds.windowutil)
+**Returns**: boolean
- `true` if installed, `false` otherwise.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| appName | string
| The application name. |
+
+
+
+### WindowUtil.launch([appNames]) ⇒ [Promise
](#user-content-bit.sds.promise)
+Launches the provided or all application(s).
+
+Please note that already running applications will not be launched by this method.
+
+Launching the application(s) is an asychronous operation, therefore this method returns a
+promise that is fulfilled with an array of `SYNO.SDS.AppInstance` objects.
+
+If you call this method without providing `appNames`, all applications that can open a
+window on the DSM desktop and are currently installed on the DiskStation will be
+launched.
+
+**Kind**: static method of [WindowUtil
](#user-content-bit.sds.windowutil)
+**Returns**: [Promise
](#user-content-bit.sds.promise) - A promise for an array of `SYNO.SDS.AppInstance` objects.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| [appNames] | Array.<string>
\| string
| The application name(s). |
+
+
+
+### WindowUtil.logDefaultSize([appNames])
+Logs the respective default window size(s) of the provided or all application(s) to the
+console in CSV format. The record format is: `,,`
+
+To get the default window size, the method [getDefaultSize](#user-content-bit.sds.windowutil.getdefaultsize) is
+called, therefore please note:
+
+- The default window size can only be retrieved for currently installed applications.
+- The applications must not be running when calling this method.
+- The current restore size and XY position will be reset for those applications.
+
+If you call this method without providing `appNames`, the default window sizes of all
+applications that can open a window on the DSM desktop and are currently installed on the
+DiskStation will be logged.
+
+**Kind**: static method of [WindowUtil
](#user-content-bit.sds.windowutil)
+
+| Param | Type | Description |
+| --- | --- | --- |
+| [appNames] | Array.<string>
\| string
| The application name(s). |
+
+**Example**
+```js
+BIT.SDS.WindowUtil.logDefaultSize();
+// SYNO.SDS.AdminCenter.Application;994;570
+// SYNO.SDS.App.FileStation3.Instance;920;560
+// ...
+```
+**Example**
+```js
+BIT.SDS.WindowUtil.logDefaultSize("SYNO.SDS.App.FileStation3.Instance");
+// SYNO.SDS.App.FileStation3.Instance;920;560
+```
+
+
+### WindowUtil.resetRestoreSizeAndPosition([appNames])
+Resets the restore size and XY position of the provided application(s).
+
+If you call this method without providing `appNames`, all applications that can open a
+window on the DSM desktop and are available for the DSM version currently installed on
+the DiskStation will be reset.
+
+**Note**: Currently open application windows will not change their size and position. You
+must close and reopen the application window to see the result. Do not move or resize the
+application window beforehand, as this immediately sets the restore size and position to
+the current window size and position.
+
+**Kind**: static method of [WindowUtil
](#user-content-bit.sds.windowutil)
+
+| Param | Type | Description |
+| --- | --- | --- |
+| [appNames] | Array.<string>
\| string
| The application name(s). |
+
+**Example**
+```js
+BIT.SDS.WindowUtil.resetRestoreSizeAndPosition();
+// Resets the window size and position for all applications
+```
+**Example**
+```js
+BIT.SDS.WindowUtil.resetRestoreSizeAndPosition("SYNO.SDS.PkgManApp.Instance");
+// Resets the window size and position for Package Center
+```
+**Example**
+```js
+BIT.SDS.WindowUtil.resetRestoreSizeAndPosition(["SYNO.SDS.PkgManApp.Instance", "SYNO.SDS.HA.Instance"]);
+// Resets the window size and position for Package Center and High Availability Manager
+```
+
+
+### WindowUtil.setRestorePagePosition(appName, x, y)
+Sets the restore page XY position of the provided application.
+
+**Note**: Currently open application windows will not change their size and position. You
+must close and reopen the application window to see the result. Do not move or resize the
+application window beforehand, as this immediately sets the restore size and position to
+the current window size and position.
+
+**Kind**: static method of [WindowUtil
](#user-content-bit.sds.windowutil)
+
+| Param | Type | Description |
+| --- | --- | --- |
+| appName | string
| The application name. |
+| x | number
| The page x position. |
+| y | number
| The page y position. |
+
+**Example**
+```js
+BIT.SDS.WindowUtil.setRestorePagePosition("SYNO.SDS.App.FileStation3.Instance", 10, 49);
+// Sets the restore page XY position for File Station windows to (10px, 49px)
+```
+
+
+### WindowUtil.suggestBounds() ⇒ [Bounds
](#user-content-bit.sds.windowutil..bounds)
+Calculates a suggestion for the bounds which can be used as input for
+[cascadeOverlap](#user-content-bit.sds.windowutil.cascadeoverlap).
+
+The suggestion is based on the current size of the browser window, therefore you should
+adjust the browser window to your needs before calling this method.
+
+**Kind**: static method of [WindowUtil
](#user-content-bit.sds.windowutil)
+**Returns**: [Bounds
](#user-content-bit.sds.windowutil..bounds) - The suggested bounds.
+**Example**
+```js
+BIT.SDS.WindowUtil.suggestBounds();
+// => {x: 160, y: 139, width: 1640, height: 830}
+```
+
+
+### WindowUtil~AppData : Object
+An object containing data about an application.
+
+**Kind**: inner typedef of [WindowUtil
](#user-content-bit.sds.windowutil)
+**Properties**
+
+| Name | Type | Description |
+| --- | --- | --- |
+| appName | string
| The application name. |
+| dsmVersions | Array.<string>
| An array of DSM versions. |
+| maxDefaultWidth | number
| The maximum default window width. |
+| maxDefaultHeight | number
| The maximum default window height. |
+
+
+
+### WindowUtil~AppWinSize : Object
+An object containing the size of an application window.
+
+**Kind**: inner typedef of [WindowUtil
](#user-content-bit.sds.windowutil)
+**Properties**
+
+| Name | Type | Description |
+| --- | --- | --- |
+| appName | string
| The application name. |
+| width | number
| The window width. |
+| height | number
| The window height. |
+
+
+
+### WindowUtil~Bounds : Object
+A rectangle defining the bounds for the position of the application windows.
+
+**Kind**: inner typedef of [WindowUtil
](#user-content-bit.sds.windowutil)
+**Properties**
+
+| Name | Type | Description |
+| --- | --- | --- |
+| x | number
| The bounds page x position. |
+| y | number
| The bounds page y position. |
+| width | number
| The bounds width. |
+| height | number
| The bounds height. |
+
+
+
+### SDS.Promise
+**Kind**: static class of [SDS
](#user-content-bit.sds)
+
+* [.Promise](#user-content-bit.sds.promise)
+ * [new Promise(resolver)](#user-content-new_bit.sds.promise_new)
+ * _instance_
+ * [.catch(onRejected)](#user-content-bit.sds.promise+catch) ⇒ [Promise
](#user-content-bit.sds.promise)
+ * [.finally(onFinally)](#user-content-bit.sds.promise+finally) ⇒ [Promise
](#user-content-bit.sds.promise)
+ * [.reject(reason)](#user-content-bit.sds.promise+reject)
+ * [.resolve(value)](#user-content-bit.sds.promise+resolve)
+ * [.then(onFulfilled, onRejected)](#user-content-bit.sds.promise+then) ⇒ [Promise
](#user-content-bit.sds.promise)
+ * _static_
+ * [.all(promises)](#user-content-bit.sds.promise.all) ⇒ [Promise
](#user-content-bit.sds.promise)
+ * [.race(promises)](#user-content-bit.sds.promise.race) ⇒ [Promise
](#user-content-bit.sds.promise)
+ * [.reject(reason)](#user-content-bit.sds.promise.reject) ⇒ [Promise
](#user-content-bit.sds.promise)
+ * [.resolve(value)](#user-content-bit.sds.promise.resolve) ⇒ [Promise
](#user-content-bit.sds.promise)
+ * [.retry(fn, tries, delay)](#user-content-bit.sds.promise.retry) ⇒ [Promise
](#user-content-bit.sds.promise)
+
+
+
+### new Promise(resolver)
+Creates a new [Promise](#user-content-bit.sds.promise) instance.
+
+
+| Param | Type | Description |
+| --- | --- | --- |
+| resolver | function
| The resolver function. |
+
+
+
+### promise.catch(onRejected) ⇒ [Promise
](#user-content-bit.sds.promise)
+Adds a callback to the promise to be called when this promise is rejected. Returns a new
+promise that will be rejected when the callback is complete.
+
+**Kind**: instance method of [Promise
](#user-content-bit.sds.promise)
+**Returns**: [Promise
](#user-content-bit.sds.promise) - A new promise.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| onRejected | function
| The rejected callback. |
+
+
+
+### promise.finally(onFinally) ⇒ [Promise
](#user-content-bit.sds.promise)
+Adds a callback to the promise to be called when this promise is fulfilled or rejected.
+Returns a new promise that will be fulfilled or rejected when the callback is complete.
+
+**Kind**: instance method of [Promise
](#user-content-bit.sds.promise)
+**Returns**: [Promise
](#user-content-bit.sds.promise) - A new promise.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| onFinally | function
| The finally callback. |
+
+
+
+### promise.reject(reason)
+Rejects the promise with the passed reason.
+
+**Kind**: instance method of [Promise
](#user-content-bit.sds.promise)
+
+| Param | Type | Description |
+| --- | --- | --- |
+| reason | \*
| The reason. |
+
+
+
+### promise.resolve(value)
+Resolves the promise with the passed value.
+
+**Kind**: instance method of [Promise
](#user-content-bit.sds.promise)
+
+| Param | Type | Description |
+| --- | --- | --- |
+| value | \*
| The value. |
+
+
+
+### promise.then(onFulfilled, onRejected) ⇒ [Promise
](#user-content-bit.sds.promise)
+Adds callbacks to the promise to be called when this promise is fulfilled or rejected.
+Returns a new promise that will be fulfilled or rejected when the callback is complete.
+
+**Kind**: instance method of [Promise
](#user-content-bit.sds.promise)
+**Returns**: [Promise
](#user-content-bit.sds.promise) - A new promise.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| onFulfilled | function
| The fulfilled callback. |
+| onRejected | function
| The rejected callback. |
+
+
+
+### Promise.all(promises) ⇒ [Promise
](#user-content-bit.sds.promise)
+Returns a promise that resolves when all of the promises passed have resolved or when the
+array contains no promises. It rejects with the reason of the first promise that rejects.
+
+**Kind**: static method of [Promise
](#user-content-bit.sds.promise)
+**Returns**: [Promise
](#user-content-bit.sds.promise) - A new promise.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| promises | [Array.<Promise>
](#user-content-bit.sds.promise) | The promises. |
+
+
+
+### Promise.race(promises) ⇒ [Promise
](#user-content-bit.sds.promise)
+Returns a new promise that resolves or rejects as soon as one of the promises passed
+resolves or rejects, with the value or reason from that promise.
+
+**Kind**: static method of [Promise
](#user-content-bit.sds.promise)
+**Returns**: [Promise
](#user-content-bit.sds.promise) - A new promise.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| promises | [Array.<Promise>
](#user-content-bit.sds.promise) | The promises. |
+
+
+
+### Promise.reject(reason) ⇒ [Promise
](#user-content-bit.sds.promise)
+Returns a promise that is rejected with the passed reason.
+
+**Kind**: static method of [Promise
](#user-content-bit.sds.promise)
+**Returns**: [Promise
](#user-content-bit.sds.promise) - A new rejecting promise.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| reason | \*
| The reason. |
+
+
+
+### Promise.resolve(value) ⇒ [Promise
](#user-content-bit.sds.promise)
+Returns a promise that is resolved with the passed value.
+
+**Kind**: static method of [Promise
](#user-content-bit.sds.promise)
+**Returns**: [Promise
](#user-content-bit.sds.promise) - A new resolving promise.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| value | \*
| The value. |
+
+
+
+### Promise.retry(fn, tries, delay) ⇒ [Promise
](#user-content-bit.sds.promise)
+Tries to resolve a promise multiple times if it is rejected. The passed function will be
+called repeatedly until the promise returned by the function is resolved or the maximum
+number of attempts is reached.
+
+Returns a new promise that is fulfilled with the fulfillment value of the promise
+returned by the function, or is rejected with the same reason as the promise returned by
+the function in the last attempt.
+
+Subsequent calls of the function will be deferred by a delay specified in milliseconds.
+
+**Kind**: static method of [Promise
](#user-content-bit.sds.promise)
+**Returns**: [Promise
](#user-content-bit.sds.promise) - A new promise.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| fn | function
| The function returning a promise. |
+| tries | number
| The maximum number of attempts. |
+| delay | number
| The delay. |
+
+**Example**
+```js
+function trySomething() {
+ function resolver(resolve, reject) {
+ ... // Try to resolve promise
+ }
+
+ return new BIT.SDS.Promise(resolver);
+}
+
+BIT.SDS.Promise.retry(trySomething, 5, 5000)
+ .then(...)
+ .catch(...);
+```
+## License
+
+Copyright (C) 2019 Michael Berger (BITespresso)
+
+DSM-Window-Utilities is licensed under the **GNU General Public License v3 (GPL-3)** (http://www.gnu.org/copyleft/gpl.html).
diff --git a/bit-sds-windowutil.js b/bit-sds-windowutil.js
new file mode 100644
index 0000000..1a9d264
--- /dev/null
+++ b/bit-sds-windowutil.js
@@ -0,0 +1,1123 @@
+/**
+ * BITespresso namespace.
+ * @namespace BIT
+ */
+Ext.namespace("BIT");
+
+/**
+ * Synology DiskStation namespace.
+ * @namespace BIT.SDS
+ */
+Ext.namespace("BIT.SDS");
+
+Ext.namespace("BIT.SDS.Promise");
+
+Ext.define("BIT.SDS.Promise",
+/**
+ * @lends BIT.SDS.Promise.prototype
+ */
+{
+ /**
+ * @lends BIT.SDS.Promise
+ */
+ statics: {
+ STATE: {
+ PENDING: "pending",
+ FULFILLED: "fulfilled",
+ REJECTED: "rejected"
+ },
+
+ /**
+ * Returns a promise that resolves when all of the promises passed have resolved or when the
+ * array contains no promises. It rejects with the reason of the first promise that rejects.
+ *
+ * @param {BIT.SDS.Promise[]} promises The promises.
+ * @return {BIT.SDS.Promise} A new promise.
+ */
+ all: function(promises) {
+ return new BIT.SDS.Promise(function(resolve, reject) {
+ var results = [];
+ var resolved = 0;
+
+ Ext.each(promises, function(promise, i) {
+ if (!promise || !Ext.isFunction(promise.then)) {
+ promise = BIT.SDS.Promise.resolve(promise);
+ }
+
+ promise
+ .then(function(value) {
+ results[i] = value;
+ resolved++;
+
+ if (resolved === promises.length) resolve(results);
+ })
+ .catch(function(reason) {
+ reject(reason);
+ });
+ }, this);
+
+ if (promises.length === 0) resolve(results);
+ });
+ },
+
+ /**
+ * Returns a new promise that resolves or rejects as soon as one of the promises passed
+ * resolves or rejects, with the value or reason from that promise.
+ *
+ * @param {BIT.SDS.Promise[]} promises The promises.
+ * @return {BIT.SDS.Promise} A new promise.
+ */
+ race: function(promises) {
+ return new BIT.SDS.Promise(function(resolve, reject) {
+ Ext.each(promises, function(promise) {
+ promise.then(resolve, reject);
+ }, this);
+ });
+ },
+
+ /**
+ * Returns a promise that is rejected with the passed reason.
+ *
+ * @param {*} reason The reason.
+ * @return {BIT.SDS.Promise} A new rejecting promise.
+ */
+ reject: function(reason) {
+ return new BIT.SDS.Promise(function(resolve, reject) {
+ reject(reason);
+ });
+ },
+
+ /**
+ * Returns a promise that is resolved with the passed value.
+ *
+ * @param {*} value The value.
+ * @return {BIT.SDS.Promise} A new resolving promise.
+ */
+ resolve: function(value) {
+ return new BIT.SDS.Promise(function(resolve, reject) {
+ resolve(value);
+ });
+ },
+
+ /**
+ * Tries to resolve a promise multiple times if it is rejected. The passed function will be
+ * called repeatedly until the promise returned by the function is resolved or the maximum
+ * number of attempts is reached.
+ *
+ * Returns a new promise that is fulfilled with the fulfillment value of the promise
+ * returned by the function, or is rejected with the same reason as the promise returned by
+ * the function in the last attempt.
+ *
+ * Subsequent calls of the function will be deferred by a delay specified in milliseconds.
+ *
+ * @param {Function} fn The function returning a promise.
+ * @param {number} tries The maximum number of attempts.
+ * @param {number} delay The delay.
+ * @return {BIT.SDS.Promise} A new promise.
+ *
+ * @example
+ * function trySomething() {
+ * function resolver(resolve, reject) {
+ * ... // Try to resolve promise
+ * }
+ *
+ * return new BIT.SDS.Promise(resolver);
+ * }
+ *
+ * BIT.SDS.Promise.retry(trySomething, 5, 5000)
+ * .then(...)
+ * .catch(...);
+ */
+ retry: function(fn, tries, delay) {
+ return new BIT.SDS.Promise(function(resolve, reject) {
+ var lastRejectReason;
+
+ function retry() {
+ if (tries > 0) {
+ tries--;
+ fn()
+ .then(resolve)
+ .catch(function(reason) {
+ lastRejectReason = reason;
+ setTimeout(retry, delay);
+ });
+ } else {
+ reject(lastRejectReason);
+ }
+ }
+
+ retry();
+ });
+ }
+ },
+
+ clients: undefined,
+
+ result: undefined,
+
+ state: undefined,
+
+ /**
+ * Creates a new {@link BIT.SDS.Promise} instance.
+ *
+ * @method BIT.SDS.Promise
+ * @constructs
+ *
+ * @param {Function} resolver The resolver function.
+ */
+ constructor: function(resolver) {
+ this.state = BIT.SDS.Promise.STATE.PENDING;
+ this.clients = [];
+ this.result = undefined;
+
+ function resolve(value) {
+ this.resolve(value);
+ }
+
+ function reject(reason) {
+ this.reject(reason);
+ }
+
+ if (Ext.isFunction(resolver)) {
+ try {
+ resolver(resolve.createDelegate(this), reject.createDelegate(this));
+ } catch (error) {
+ this.reject(error);
+ }
+ } else if (arguments.length > 0) {
+ throw Error("Promise resolver " + resolver + " is not a function");
+ }
+ },
+
+ /**
+ * Adds a callback to the promise to be called when this promise is rejected. Returns a new
+ * promise that will be rejected when the callback is complete.
+ *
+ * @param {Function} onRejected The rejected callback.
+ * @return {BIT.SDS.Promise} A new promise.
+ */
+ catch: function(onRejected) {
+ return this.then(null, onRejected);
+ },
+
+ /**
+ * Adds a callback to the promise to be called when this promise is fulfilled or rejected.
+ * Returns a new promise that will be fulfilled or rejected when the callback is complete.
+ *
+ * @param {Function} onFinally The finally callback.
+ * @return {BIT.SDS.Promise} A new promise.
+ */
+ finally: function(onFinally) {
+ if (!Ext.isFunction(onFinally)) return this.then(onFinally, onFinally);
+
+ function onFulfilled(value) {
+ return new BIT.SDS.Promise(function(resolve) {
+ resolve(onFinally());
+ }).then(function() {
+ return value;
+ });
+ }
+
+ function onRejected(reason) {
+ return new BIT.SDS.Promise(function(resolve) {
+ resolve(onFinally());
+ }).then(function() {
+ throw reason;
+ });
+ }
+
+ return this.then(onFulfilled.createDelegate(this), onRejected.createDelegate(this));
+ },
+
+ /**
+ * Rejects the promise with the passed reason.
+ *
+ * @param {*} reason The reason.
+ */
+ reject: function(reason) {
+ if (this.state !== BIT.SDS.Promise.STATE.PENDING) return;
+
+ this.state = BIT.SDS.Promise.STATE.REJECTED;
+ this.result = reason;
+
+ function rejectAllClients() {
+ Ext.each(this.clients, function(client) {
+ client.rejectClient(reason);
+ }, this);
+ }
+
+ setTimeout(rejectAllClients.createDelegate(this));
+ },
+
+ /**
+ * Resolves the promise with the passed value.
+ *
+ * @param {*} value The value.
+ */
+ resolve: function(value) {
+ var alreadyCalled = false;
+ var then;
+
+ function onFulfilled(value) {
+ if (!alreadyCalled) {
+ alreadyCalled = true;
+ this.resolve(value);
+ }
+ }
+
+ function onRejected(reason) {
+ if (!alreadyCalled) {
+ alreadyCalled = true;
+ this.reject(reason);
+ }
+ }
+
+ if (this.state !== BIT.SDS.Promise.STATE.PENDING) return;
+
+ if (value === this) return this.reject(Error("A promise cannot be resolved by itself"));
+
+ if (value && (Ext.isFunction(value) || Ext.isObject(value))) {
+ try {
+ then = value.then;
+
+ if (Ext.isFunction(then)) {
+ then.call(value, onFulfilled.createDelegate(this), onRejected.createDelegate(this));
+
+ return;
+ }
+ } catch (error) {
+ if (!alreadyCalled) this.reject(error);
+
+ return;
+ }
+ }
+
+ this.state = BIT.SDS.Promise.STATE.FULFILLED;
+ this.result = value;
+
+ function fulfillAllClients() {
+ Ext.each(this.clients, function(client) {
+ client.fulfillClient(value);
+ }, this);
+ }
+
+ setTimeout(fulfillAllClients.createDelegate(this));
+ },
+
+ /**
+ * Adds callbacks to the promise to be called when this promise is fulfilled or rejected.
+ * Returns a new promise that will be fulfilled or rejected when the callback is complete.
+ *
+ * @param {Function} onFulfilled The fulfilled callback.
+ * @param {Function} onRejected The rejected callback.
+ * @return {BIT.SDS.Promise} A new promise.
+ */
+ then: function(onFulfilled, onRejected) {
+ var promise = new BIT.SDS.Promise();
+ var client = {
+ onFulfilled: onFulfilled,
+ onRejected: onRejected,
+ promise: promise,
+
+ fulfillClient: function(result) {
+ if (Ext.isFunction(this.onFulfilled)) {
+ try {
+ var value = this.onFulfilled.call(undefined, result);
+ this.promise.resolve(value);
+ } catch (error) {
+ this.promise.reject(error);
+ }
+ } else {
+ this.promise.resolve(result);
+ }
+ },
+
+ rejectClient: function(result) {
+ if (Ext.isFunction(this.onRejected)) {
+ try {
+ var value = this.onRejected.call(undefined, result);
+ this.promise.resolve(value);
+ } catch (error) {
+ this.promise.reject(error);
+ }
+ } else {
+ this.promise.reject(result);
+ }
+ }
+ };
+
+ switch (this.state) {
+ case BIT.SDS.Promise.STATE.PENDING:
+ this.clients.push(client);
+ break;
+
+ case BIT.SDS.Promise.STATE.FULFILLED:
+ setTimeout(client.fulfillClient.createDelegate(client, [this.result]));
+ break;
+
+ case BIT.SDS.Promise.STATE.REJECTED:
+ setTimeout(client.rejectClient.createDelegate(client, [this.result]));
+ break;
+ }
+
+ return promise;
+ }
+});
+
+Ext.namespace("BIT.SDS.WindowUtil");
+
+/**
+ * @class BIT.SDS.WindowUtil
+ *
+ * @hideconstructor
+ */
+Ext.define("BIT.SDS.WindowUtil",
+{
+ /**
+ * @lends BIT.SDS.WindowUtil
+ */
+ statics: {
+ APP_DATA_ARRAY: [
+ ["SYNO.SDS.AdminCenter.Application", ["5.2", "6.0", "6.1", "6.2"], [ 994, 570]],
+ ["SYNO.SDS.App.FileStation3.Instance", ["5.2", "6.0", "6.1", "6.2"], [ 920, 560]],
+ ["SYNO.SDS.EzInternet.Instance", ["5.2", "6.0", "6.1", "6.2"], [ 700, 450]],
+ ["SYNO.SDS.HelpBrowser.Application", ["5.2", "6.0", "6.1", "6.2"], [ 995, 500]],
+ ["SYNO.SDS.PkgManApp.Instance", ["5.2", "6.0", "6.1", "6.2"], [1060, 580]],
+ ["SYNO.SDS.ResourceMonitor.Instance", ["5.2", "6.0", "6.1", "6.2"], [1024, 580]],
+ ["SYNO.SDS.StorageManager.Instance", ["5.2", "6.0", "6.1", "6.2"], [1000, 600]],
+ ["SYNO.SDS.HA.Instance", ["5.2", "6.0", "6.1", "6.2"], [ 990, 580]],
+ ["SYNO.SDS.LogCenter.Instance", ["5.2", "6.0", "6.1", "6.2"], [1005, 580]],
+ ["SYNO.SDS.SecurityScan.Instance", ["5.2", "6.0", "6.1", "6.2"], [ 990, 570]],
+ ["SYNO.SDS.SupportForm.Application", ["5.2", "6.0", "6.1", "6.2"], [ 960, 580]],
+ ["SYNO.SDS.App.PersonalSettings.Instance", ["5.2", "6.0", "6.1", "6.2"], [ 850, 500]],
+ ["SYNO.SDS.Backup.Application", ["5.2" ], [ 990, 580]],
+ ["SYNO.SDS.ACEEditor.Application", ["5.2" ], [ 800, 400]],
+ ["SYNO.SDS.MyDSCenter.Application", ["5.2" ], [ 990, 560]],
+ ["SYNO.SDS.StorageReport.Application", ["5.2" ], [ 965, 580]],
+ ["SYNO.SDS.AV.Instance", ["5.2", "6.0", "6.1", "6.2"], [ 952, 580]],
+ ["SYNO.SDS.AudioStation.Application", ["5.2", "6.0", "6.1", "6.2"], [ 980, 600]],
+ ["SYNO.SDS.CardDAVServer.Instance", ["5.2", "6.0", "6.1", "6.2"], [ 990, 560]],
+ ["SYNO.SDS.CSTN.Instance", ["5.2", "6.0", "6.1", "6.2"], [ 968, 580]],
+ ["SYNO.SDS.CloudStationClient.Instance", ["5.2", "6.0", "6.1", "6.2"], [ 900, 530]],
+ ["SYNO.SDS.DSCloudSync.Instance", ["5.2", "6.0", "6.1", "6.2"], [ 900, 530]],
+ ["SYNO.SDS.CMS.Application", ["5.2", "6.0", "6.1", "6.2"], [ 990, 580]],
+ ["SYNO.SDS.LDAP.AppInstance", ["5.2", "6.0", "6.1", "6.2"], [ 990, 665]],
+ ["SYNO.SDS.DNS.Instance", ["5.2", "6.0", "6.1", "6.2"], [ 990, 560]],
+ ["SYNO.SDS.Docker.Application", ["5.2", "6.0", "6.1", "6.2"], [1060, 570]],
+ ["SYNO.SDS.DownloadStation.Application", ["5.2", "6.0", "6.1", "6.2"], [ 980, 580]],
+ ["SYNO.SDS.GIT.Instance", ["5.2", "6.0", "6.1", "6.2"], [ 600, 350]],
+ ["SYNO.SDS.Glacier.Instance", ["5.2", "6.0", "6.1", "6.2"], [ 950, 480]],
+ ["SYNO.SDS.HiDrive.Instance", ["5.2", "6.0" ], [1030, 580]],
+ ["SYNO.SDS.iTunes.Application", ["5.2", "6.0", "6.1", "6.2"], [ 620, 380]],
+ ["SYNO.SDS.JAVAMANAGER.Instance", ["5.2" ], [ 600, 300]],
+ ["SYNO.SDS.MailServer.Instance", ["5.2", "6.0", "6.1", "6.2"], [ 990, 560]],
+ ["SYNO.SDS.MARIADB.Instance", ["5.2", "6.0", "6.1", "6.2"], [ 600, 500]],
+ ["SYNO.SDS.MediaServer.AppInstance", ["5.2", "6.0", "6.1", "6.2"], [ 950, 580]],
+ ["SYNO.SDS.NoteStation.Application", ["5.2", "6.0", "6.1", "6.2"], [1000, 580]],
+ ["SYNO.SDS.ProxyServer.Instance", ["5.2", "6.0", "6.1", "6.2"], [ 990, 560]],
+ ["SYNO.SDS.RAD.Instance", ["5.2", "6.0", "6.1", "6.2"], [ 990, 560]],
+ ["SYNO.SDS.SSOServer.Instance", ["5.2", "6.0", "6.1", "6.2"], [ 990, 560]],
+ ["SYNO.SDS.SVN.Instance", ["5.2", "6.0", "6.1", "6.2"], [ 600, 500]],
+ ["SYNO.SDS.VideoStation.AppInstance", ["5.2" ], [1022, 600]],
+ ["SYNO.SDS.VPN.Instance", ["5.2", "6.0", "6.1", "6.2"], [ 860, 520]],
+ ["SYNO.SDS.ActiveBackup.Instance", [ "6.0" ], [1050, 580]],
+ ["SYNO.SDS.Backup.Application", [ "6.0", "6.1", "6.2"], [ 990, 580]],
+ ["SYNO.SDS.BackupService.Instance", [ "6.0", "6.1", "6.2"], [ 810, 575]],
+ ["SYNO.SDS.JAVA7.Instance", [ "6.0", "6.1", "6.2"], [ 600, 300]],
+ ["SYNO.SDS.JAVA8.Instance", [ "6.0", "6.1", "6.2"], [ 600, 300]],
+ ["SYNO.SDS.MailPlusServer.Instance", [ "6.0", "6.1", "6.2"], [1040, 600]],
+ ["SYNO.SDS.MARIADB10.Instance", [ "6.0", "6.1", "6.2"], [ 600, 500]],
+ ["SYNO.SDS.DSMNotify.Setting.Application", [ "6.0", "6.1", "6.2"], [ 625, 580]],
+ ["SYNO.SDS.ClusteredShare.Application", [ "6.0", "6.1", "6.2"], [ 960, 570]],
+ ["SYNO.SDS.DisasterRecovery.Application", [ "6.0", "6.1", "6.2"], [ 990, 580]],
+ ["SYNO.SDS.StorageAnalyzer.Application", [ "6.0", "6.1", "6.2"], [ 965, 580]],
+ ["SYNO.TextEditor.Application", [ "6.0", "6.1", "6.2"], [ 800, 400]],
+ ["SYNO.SDS.WebStation.Application", [ "6.0", "6.1", "6.2"], [ 990, 580]],
+ ["SYNO.SDS.WebDAVServer.Instance", [ "6.0", "6.1", "6.2"], [ 990, 560]],
+ ["SYNO.Finder.Application", [ "6.1", "6.2"], [1000, 600]],
+ ["SYNO.SDS.ActiveBackupGSuite.Instance", [ "6.1", "6.2"], [1184, 592]],
+ ["SYNO.SDS.ActiveBackupOffice365.Instance", [ "6.1", "6.2"], [1184, 592]],
+ ["SYNO.SDS.PrestoServer.Application", [ "6.1", "6.2"], [1024, 580]],
+ ["SYNO.SDS.USBCopy.Application", [ "6.1", "6.2"], [ 900, 530]],
+ ["SYNO.ActiveBackup.AppInstance", [ "6.1", "6.2"], [1184, 580]],
+ ["SYNO.SDS.iSCSI.Application", [ "6.2"], [1042, 580]],
+ ["SYNO.SDS.OAuthService.Instance", [ "6.2"], [ 990, 560]],
+ ["SYNO.SDS.ADServer.Application", [ "6.2"], [ 990, 560]],
+ ["SYNO.SDS.Virtualization.Application", [ "6.2"], [1038, 637]]
+ ],
+
+ nextAsyncActionTime: 0,
+
+ appData: [],
+
+ /**
+ * Sets the restore XY position of all applications to cascaded, overlapping positions
+ * within the specified bounds and resets the restore size so that the windows will have
+ * their respective default sizes.
+ *
+ * The algorithm used ensures that each window has a position that depends entirely on the
+ * specified bounds, regardless of which applications are installed or which DSM version is
+ * used.
+ *
+ * The method call including the parameters used is logged in the console. Please note this
+ * down for your later information.
+ *
+ * **Note 1**: Currently open application windows will not change their size and position.
+ * You must close and reopen the application window to see the result. Do not move or resize
+ * the application window beforehand, as this immediately sets the restore size and position
+ * to the current window size and position.
+ *
+ * **Note 2**: The Synology CMS (Central Management System) application
+ * (`SYNO.SDS.CMS.Application`) does not read the restore size and position due to a bug in
+ * DSM. To ensure that this window has the correct size and position, each time this method
+ * is called, the application will be launched and the window will be set to the correct
+ * size and position.
+ *
+ * @param {BIT.SDS.WindowUtil~Bounds} [bounds] The bounds.
+ *
+ * @example
+ * BIT.SDS.WindowUtil.cascadeOverlap();
+ *
+ * @example
+ * BIT.SDS.WindowUtil.cascadeOverlap({x: 160, y: 139, width: 1640, height: 830});
+ */
+ cascadeOverlap: function(bounds) {
+ var boundsAsLiteral;
+ var boundsBottomRightCorner;
+ var offsetX;
+ var offsetY;
+
+ var dsmVersion = BIT.SDS.WindowUtil.getDsmVersion();
+
+ if (bounds === undefined) bounds = BIT.SDS.WindowUtil.suggestBounds();
+
+ boundsAsLiteral = "{x: " + bounds.x + ", y: " + bounds.y + ", width: " + bounds.width + ", height: " + bounds.height + "}";
+
+ console.log("Using: BIT.SDS.WindowUtil.cascadeOverlap(" + boundsAsLiteral + ");");
+
+ offsetX = bounds.x;
+ offsetY = bounds.y;
+
+ boundsBottomRightCorner = {
+ x: bounds.x + bounds.width,
+ y: bounds.y + bounds.height
+ };
+
+ Ext.each(BIT.SDS.WindowUtil.getAppData(), function(appData) {
+ var windowBottomRightCorner;
+ var appInstances;
+
+ windowBottomRightCorner = {
+ x: offsetX + appData.maxDefaultWidth,
+ y: offsetY + appData.maxDefaultHeight
+ };
+
+ if (windowBottomRightCorner.x > boundsBottomRightCorner.x && windowBottomRightCorner.y > boundsBottomRightCorner.y) {
+ offsetX = bounds.x;
+ offsetY = bounds.y;
+ } else {
+ if (windowBottomRightCorner.x > boundsBottomRightCorner.x) {
+ if (offsetX === bounds.x) {
+ offsetY = bounds.y;
+ }
+ offsetX = bounds.x;
+ } else {
+ if (windowBottomRightCorner.y > boundsBottomRightCorner.y) {
+ // offsetX += 30;
+ offsetY = bounds.y;
+ }
+ }
+ }
+
+ if (windowBottomRightCorner.x > boundsBottomRightCorner.x && windowBottomRightCorner.y > boundsBottomRightCorner.y) {
+ offsetX = bounds.x;
+ offsetY = bounds.y;
+ } else {
+ if (windowBottomRightCorner.x > boundsBottomRightCorner.x) {
+ if (offsetX === bounds.x) {
+ offsetY = bounds.y;
+ }
+ offsetX = bounds.x;
+ } else {
+ if (windowBottomRightCorner.y > boundsBottomRightCorner.y) {
+ // offsetX += 30;
+ offsetY = bounds.y;
+ }
+ }
+ }
+
+ if (appData.dsmVersions.indexOf(dsmVersion) !== -1) {
+ BIT.SDS.WindowUtil.resetRestoreSizeAndPosition(appData.appName);
+ BIT.SDS.WindowUtil.setRestorePagePosition(appData.appName, offsetX, offsetY);
+
+ if (appData.appName === "SYNO.SDS.CMS.Application") {
+ appInstances = SYNO.SDS.AppMgr.getByAppName(appData.appName);
+
+ if ((appInstances.length === 0) && BIT.SDS.WindowUtil.isInstalled(appData.appName)) {
+ SYNO.SDS.AppLaunch(appData.appName, {}, false, (function() {
+ var x = offsetX;
+ var y = offsetY;
+
+ return function(appInstance) {
+ if (Ext.isObject(appInstance)) {
+ appInstance.window.setPagePosition(x, y);
+ }
+ };
+ })(), this);
+ }
+ }
+ }
+
+ offsetX += 30;
+ offsetY += 30;
+ }, this);
+ },
+
+ /**
+ * Closes the provided or all application(s).
+ *
+ * Closing application(s) is an asychronous operation, therefore this method returns a
+ * promise that is fulfilled when all provided applications are closed.
+ *
+ * If you call this method without providing `appNames`, all applications that can open a
+ * window on the DSM desktop and are currently installed on the DiskStation will be closed.
+ *
+ * @param {string[]|string} [appNames] The application name(s).
+ * @return {BIT.SDS.Promise} A promise.
+ */
+ close: function(appNames) {
+ var MAX_TRIES = 3;
+ var DELAY_BETWEEN_TRIES = 5000;
+ var DELAY_AFTER_CLOSE = 500;
+ var DELAY_BEFORE_CHECK = 2000;
+
+ var appNamesForClose = [];
+ var promises = [];
+
+ function closeApp(appName) {
+ var promises = [];
+
+ Ext.each(SYNO.SDS.AppMgr.getByAppName(appName), function(appInstance) {
+ promises.push(BIT.SDS.Promise.retry(closeAppInstance.createDelegate(this, [appInstance]), MAX_TRIES, DELAY_BETWEEN_TRIES));
+ }, this);
+
+ return BIT.SDS.Promise.all(promises);
+ }
+
+ function closeAppInstance(appInstance) {
+ var delay;
+
+ if (!appInstance.window) return BIT.SDS.Promise.resolve();
+
+ delay = BIT.SDS.WindowUtil.getAndAddDelayForAsyncAction(DELAY_AFTER_CLOSE);
+ appInstance.window.close.defer(delay, appInstance.window);
+
+ return new BIT.SDS.Promise(function(resolve, reject) {
+ setTimeout(function() {
+ if (!appInstance.window) {
+ resolve();
+ } else {
+ reject();
+ }
+ }, delay + DELAY_BEFORE_CHECK);
+ });
+ }
+
+ if (appNames === undefined) appNames = BIT.SDS.WindowUtil.getAppNamesForDsmVersion();
+
+ Ext.each(appNames, function(appName) {
+ if (BIT.SDS.WindowUtil.isAppNameForDsmVersion(appName) && BIT.SDS.WindowUtil.isInstalled(appName) && BIT.SDS.WindowUtil.hasRunningInstance(appName)) {
+ appNamesForClose.push(appName);
+ }
+ }, this);
+
+ Ext.each(appNamesForClose, function(appName) {
+ promises.push(closeApp(appName));
+ }, this);
+
+ return BIT.SDS.Promise.all(promises);
+ },
+
+ getAndAddDelayForAsyncAction: function(delayToAdd) {
+ var currentTime = new Date().getTime();
+ var delay = BIT.SDS.WindowUtil.nextAsyncActionTime - currentTime;
+
+ delay = (delay > 0) ? delay : 0;
+ BIT.SDS.WindowUtil.nextAsyncActionTime = currentTime + delay + delayToAdd;
+
+ return delay;
+ },
+
+ /**
+ * An object containing data about an application.
+ *
+ * @typedef {Object} BIT.SDS.WindowUtil~AppData
+ * @property {string} appName The application name.
+ * @property {string[]} dsmVersions An array of DSM versions.
+ * @property {number} maxDefaultWidth The maximum default window width.
+ * @property {number} maxDefaultHeight The maximum default window height.
+ */
+
+ /**
+ * Returns an array of {@link BIT.SDS.WindowUtil~AppData} for all supported applications.
+ *
+ * @return {BIT.SDS.WindowUtil~AppData[]} An array of `AppData` objects.
+ */
+ getAppData: function() {
+ if (!Ext.isArray(BIT.SDS.WindowUtil.appData) || (BIT.SDS.WindowUtil.appData.length !== BIT.SDS.WindowUtil.APP_DATA_ARRAY.length)) {
+ BIT.SDS.WindowUtil.appData = [];
+
+ Ext.each(BIT.SDS.WindowUtil.APP_DATA_ARRAY, function(appDataElement) {
+ var appData = {
+ appName: appDataElement[0],
+ dsmVersions: appDataElement[1],
+ maxDefaultWidth: appDataElement[2][0],
+ maxDefaultHeight: appDataElement[2][1]
+ };
+
+ BIT.SDS.WindowUtil.appData.push(appData);
+ }, this);
+ }
+
+ return BIT.SDS.WindowUtil.appData;
+ },
+
+ /**
+ * Returns an array of all applications that can open a window on the DSM desktop.
+ *
+ * @return {string[]} An array of application names.
+ *
+ * @example
+ * BIT.SDS.WindowUtil.getAppNames();
+ * // => ["SYNO.SDS.AdminCenter.Application", ...]
+ */
+ getAppNames: function() {
+ var appNames = [];
+
+ Ext.each(BIT.SDS.WindowUtil.getAppData(), function(appData) {
+ appNames.push(appData.appName);
+ }, this);
+
+ return appNames;
+ },
+
+ /**
+ * Returns an array of all applications that can open a window on the DSM desktop and are
+ * available for the DSM version currently installed on the DiskStation.
+ *
+ * @return {string[]} An array of application names.
+ *
+ * @example
+ * BIT.SDS.WindowUtil.getAppNamesForDsmVersion();
+ * // => ["SYNO.SDS.AdminCenter.Application", ...]
+ */
+ getAppNamesForDsmVersion: function() {
+ var appNames = [];
+ var dsmVersion = BIT.SDS.WindowUtil.getDsmVersion();
+
+ Ext.each(BIT.SDS.WindowUtil.getAppData(), function(appData) {
+ if (appData.dsmVersions.indexOf(dsmVersion) !== -1) appNames.push(appData.appName);
+ }, this);
+
+ return appNames;
+ },
+
+ /**
+ * An object containing the size of an application window.
+ *
+ * @typedef {Object} BIT.SDS.WindowUtil~AppWinSize
+ * @property {string} appName The application name.
+ * @property {number} width The window width.
+ * @property {number} height The window height.
+ */
+
+ /**
+ * Retrieves the respective default window size(s) of the provided or all application(s).
+ *
+ * To get the default window size, first the restore size and XY position are reset via
+ * {@link BIT.SDS.WindowUtil.resetRestoreSizeAndPosition}, next the application is launched
+ * and finally the size of the newly opened application window is retrieved.
+ *
+ * Therefore please note:
+ *
+ * - The default window size can only be retrieved for currently installed applications.
+ * - The applications must not be running when calling this method.
+ * - The current restore size and XY position will be reset for those applications.
+ *
+ * Launching the application(s) is an asychronous operation, therefore this method returns a
+ * promise that is fulfilled with an array of {@link BIT.SDS.WindowUtil~AppWinSize} objects.
+ *
+ * If you call this method without providing `appNames`, the default window sizes of all
+ * applications that can open a window on the DSM desktop and are currently installed on the
+ * DiskStation will be retrieved.
+ *
+ * @param {string[]|string} [appNames] The application name(s).
+ * @return {BIT.SDS.Promise} A promise for an array of `AppWinSize` objects.
+ */
+ getDefaultSize: function(appNames) {
+ var appNamesForResetAndLaunch = [];
+ var promisesForAppWinsize = [];
+
+ function getAppWinSize(appInstance) {
+ var appWindow = appInstance.window;
+ var appWinSize = {
+ appName: appInstance.jsConfig.jsID,
+ };
+
+ if (appWindow.maximized || appWindow.hidden) {
+ if (appWindow.restoreSize) {
+ appWinSize.width = appWindow.restoreSize.width;
+ appWinSize.height = appWindow.restoreSize.height;
+ } else {
+ appWinSize.width = appWindow.width;
+ appWinSize.height = appWindow.height;
+ }
+ } else {
+ appWinSize.width = appWindow.getWidth();
+ appWinSize.height = appWindow.getHeight();
+ }
+
+ return appWinSize;
+ }
+
+ if (appNames === undefined) appNames = BIT.SDS.WindowUtil.getAppNamesForDsmVersion();
+
+ Ext.each(appNames, function(appName) {
+ if (BIT.SDS.WindowUtil.isAppNameForDsmVersion(appName) && BIT.SDS.WindowUtil.isInstalled(appName) && !BIT.SDS.WindowUtil.hasRunningInstance(appName)) {
+ appNamesForResetAndLaunch.push(appName);
+ }
+ }, this);
+
+ Ext.each(appNamesForResetAndLaunch, function(appName) {
+ BIT.SDS.WindowUtil.resetRestoreSizeAndPosition(appName);
+ promisesForAppWinsize.push(BIT.SDS.WindowUtil.launch(appName)
+ .then(function(appInstances) {
+ return getAppWinSize(appInstances[0]);
+ })
+ );
+ }, this);
+
+ return BIT.SDS.Promise.all(promisesForAppWinsize);
+ },
+
+ /**
+ * Returns the DSM version currently installed on the DiskStation. The version has the
+ * format: `.`
+ *
+ * @return {string} The DSM version.
+ *
+ * @example
+ * BIT.SDS.WindowUtil.getDsmVersion();
+ * // => 6.2
+ */
+ getDsmVersion: function() {
+ return _S("majorversion") + "." + _S("minorversion");
+ },
+
+ /**
+ * Returns the name of the property that holds the restore size and position of the
+ * application window.
+ *
+ * @param {string} appName The application name.
+ * @return {string} The property name.
+ */
+ getRestoreSizePosPropertyName: function(appName) {
+ var restoreSizePosPropertyName = "restoreSizePos";
+ var dsmVersion = BIT.SDS.WindowUtil.getDsmVersion();
+
+ if (appName === "SYNO.SDS.HA.Instance") {
+ if (["5.2", "6.0", "6.1"].indexOf(dsmVersion) !== -1) {
+ restoreSizePosPropertyName = "restoreSizePos";
+ } else {
+ restoreSizePosPropertyName = "bindHAWizardWindowRestoreSizePos";
+ }
+ }
+
+ return restoreSizePosPropertyName;
+ },
+
+ /**
+ * Returns `true` if the provided application has at least one running instance.
+ *
+ * @param {string} appName The application name.
+ * @return {boolean} `true` if has running instance, `false` otherwise.
+ */
+ hasRunningInstance: function(appName) {
+ return (SYNO.SDS.AppMgr.getByAppName(appName).length > 0);
+ },
+
+ /**
+ * Returns `true` if the provided application name is an application that can open a window
+ * on the DSM desktop and is available for the DSM version currently installed on the
+ * DiskStation.
+ *
+ * @param {string} appName The application name.
+ * @return {boolean} `true` if valid, `false` otherwise.
+ */
+ isAppNameForDsmVersion: function(appName) {
+ return (BIT.SDS.WindowUtil.getAppNamesForDsmVersion().indexOf(appName) !== -1);
+ },
+
+ /**
+ * Returns `true` if the provided application is currently installed on the DiskStation.
+ *
+ * @param {string} appName The application name.
+ * @return {boolean} `true` if installed, `false` otherwise.
+ */
+ isInstalled: function(appName) {
+ return (SYNO.SDS.AppUtil.getApps().indexOf(appName) !== -1);
+ },
+
+ /**
+ * Launches the provided or all application(s).
+ *
+ * Please note that already running applications will not be launched by this method.
+ *
+ * Launching the application(s) is an asychronous operation, therefore this method returns a
+ * promise that is fulfilled with an array of `SYNO.SDS.AppInstance` objects.
+ *
+ * If you call this method without providing `appNames`, all applications that can open a
+ * window on the DSM desktop and are currently installed on the DiskStation will be
+ * launched.
+ *
+ * @param {string[]|string} [appNames] The application name(s).
+ * @return {BIT.SDS.Promise} A promise for an array of `SYNO.SDS.AppInstance` objects.
+ */
+ launch: function(appNames) {
+ var MAX_TRIES = 3;
+ var DELAY_BETWEEN_TRIES = 10000;
+ var DELAY_AFTER_LAUNCH = 1000;
+ var DELAY_BEFORE_RESOLVE = 2000;
+ var LAUNCH_TIMEOUT = 30000;
+
+ var appNamesForLaunch = [];
+ var promisesForAppInstance = [];
+
+ function launchApp(appName) {
+ var delay;
+ var promiseForAppInstance;
+ var rejectAfterTimeoutPromise;
+
+ delay = BIT.SDS.WindowUtil.getAndAddDelayForAsyncAction(DELAY_AFTER_LAUNCH);
+
+ promiseForAppInstance = new BIT.SDS.Promise(function(resolve, reject) {
+ SYNO.SDS.AppLaunch.defer(delay, this, [appName, {}, false, function(appInstance) {
+ var oldInstances;
+
+ if (appInstance) {
+ resolve.defer(DELAY_BEFORE_RESOLVE, this, [appInstance]);
+ } else {
+ oldInstances = SYNO.SDS.AppMgr.getByAppName(appName);
+
+ if (oldInstances.length > 0) {
+ resolve.defer(DELAY_BEFORE_RESOLVE, this, [oldInstances[oldInstances.length - 1]]);
+ } else {
+ reject();
+ }
+ }
+ }, this]);
+ });
+
+ rejectAfterTimeoutPromise = new BIT.SDS.Promise(function(resolve, reject) {
+ setTimeout(function() {
+ reject();
+ }, delay + LAUNCH_TIMEOUT);
+ });
+
+ return BIT.SDS.Promise.race([promiseForAppInstance, rejectAfterTimeoutPromise]);
+ }
+
+ if (appNames === undefined) appNames = BIT.SDS.WindowUtil.getAppNamesForDsmVersion();
+
+ Ext.each(appNames, function(appName) {
+ if (BIT.SDS.WindowUtil.isAppNameForDsmVersion(appName) && BIT.SDS.WindowUtil.isInstalled(appName) && !BIT.SDS.WindowUtil.hasRunningInstance(appName)) {
+ appNamesForLaunch.push(appName);
+ }
+ }, this);
+
+ Ext.each(appNamesForLaunch, function(appName) {
+ promisesForAppInstance.push(BIT.SDS.Promise.retry(launchApp.createDelegate(this, [appName]), MAX_TRIES, DELAY_BETWEEN_TRIES));
+ }, this);
+
+ return BIT.SDS.Promise.all(promisesForAppInstance);
+ },
+
+ /**
+ * Logs the respective default window size(s) of the provided or all application(s) to the
+ * console in CSV format. The record format is: `,,`
+ *
+ * To get the default window size, the method {@link BIT.SDS.WindowUtil.getDefaultSize} is
+ * called, therefore please note:
+ *
+ * - The default window size can only be retrieved for currently installed applications.
+ * - The applications must not be running when calling this method.
+ * - The current restore size and XY position will be reset for those applications.
+ *
+ * If you call this method without providing `appNames`, the default window sizes of all
+ * applications that can open a window on the DSM desktop and are currently installed on the
+ * DiskStation will be logged.
+ *
+ * @param {string[]|string} [appNames] The application name(s).
+ *
+ * @example
+ * BIT.SDS.WindowUtil.logDefaultSize();
+ * // SYNO.SDS.AdminCenter.Application;994;570
+ * // SYNO.SDS.App.FileStation3.Instance;920;560
+ * // ...
+ *
+ * @example
+ * BIT.SDS.WindowUtil.logDefaultSize("SYNO.SDS.App.FileStation3.Instance");
+ * // SYNO.SDS.App.FileStation3.Instance;920;560
+ */
+ logDefaultSize: function(appNames) {
+ BIT.SDS.WindowUtil.getDefaultSize(appNames)
+ .then(function(appWinSizes) {
+ Ext.each(appWinSizes, function(appWinSize) {
+ console.log(appWinSize.appName + "," + appWinSize.width + "," + appWinSize.height);
+ }, this);
+ })
+ .catch(function(reason) {
+ console.log("Error retrieving window size: " + ((reason instanceof Error) ? reason.message : reason));
+ });
+ },
+
+ /**
+ * Resets the restore size and XY position of the provided application(s).
+ *
+ * If you call this method without providing `appNames`, all applications that can open a
+ * window on the DSM desktop and are available for the DSM version currently installed on
+ * the DiskStation will be reset.
+ *
+ * **Note**: Currently open application windows will not change their size and position. You
+ * must close and reopen the application window to see the result. Do not move or resize the
+ * application window beforehand, as this immediately sets the restore size and position to
+ * the current window size and position.
+ *
+ * @param {string[]|string} [appNames] The application name(s).
+ *
+ * @example
+ * BIT.SDS.WindowUtil.resetRestoreSizeAndPosition();
+ * // Resets the window size and position for all applications
+ *
+ * @example
+ * BIT.SDS.WindowUtil.resetRestoreSizeAndPosition("SYNO.SDS.PkgManApp.Instance");
+ * // Resets the window size and position for Package Center
+ *
+ * @example
+ * BIT.SDS.WindowUtil.resetRestoreSizeAndPosition(["SYNO.SDS.PkgManApp.Instance", "SYNO.SDS.HA.Instance"]);
+ * // Resets the window size and position for Package Center and High Availability Manager
+ */
+ resetRestoreSizeAndPosition: function(appNames) {
+ if (appNames === undefined) appNames = BIT.SDS.WindowUtil.getAppNamesForDsmVersion();
+
+ Ext.each(appNames, function(appName) {
+ var restoreSizePosPropertyName;
+
+ if (BIT.SDS.WindowUtil.isAppNameForDsmVersion(appName)) {
+ restoreSizePosPropertyName = BIT.SDS.WindowUtil.getRestoreSizePosPropertyName(appName);
+ SYNO.SDS.UserSettings.removeProperty(appName, restoreSizePosPropertyName);
+ }
+ }, this);
+ },
+
+ /**
+ * Sets the restore page XY position of the provided application.
+ *
+ * **Note**: Currently open application windows will not change their size and position. You
+ * must close and reopen the application window to see the result. Do not move or resize the
+ * application window beforehand, as this immediately sets the restore size and position to
+ * the current window size and position.
+ *
+ * @param {string} appName The application name.
+ * @param {number} x The page x position.
+ * @param {number} y The page y position.
+ *
+ * @example
+ * BIT.SDS.WindowUtil.setRestorePagePosition("SYNO.SDS.App.FileStation3.Instance", 10, 49);
+ * // Sets the restore page XY position for File Station windows to (10px, 49px)
+ */
+ setRestorePagePosition: function(appName, x, y) {
+ var restoreSizePosPropertyName;
+ var restoreSizePos;
+
+ if (BIT.SDS.WindowUtil.isAppNameForDsmVersion(appName)) {
+ restoreSizePosPropertyName = BIT.SDS.WindowUtil.getRestoreSizePosPropertyName(appName);
+ restoreSizePos = SYNO.SDS.UserSettings.getProperty(appName, restoreSizePosPropertyName) || {};
+
+ delete restoreSizePos.x;
+ delete restoreSizePos.y;
+
+ Ext.apply(restoreSizePos, {
+ pageX: x,
+ pageY: y,
+ fromRestore: true
+ });
+
+ SYNO.SDS.UserSettings.setProperty(appName, restoreSizePosPropertyName, restoreSizePos);
+ }
+ },
+
+ /**
+ * A rectangle defining the bounds for the position of the application windows.
+ *
+ * @typedef {Object} BIT.SDS.WindowUtil~Bounds
+ * @property {number} x The bounds page x position.
+ * @property {number} y The bounds page y position.
+ * @property {number} width The bounds width.
+ * @property {number} height The bounds height.
+ */
+
+ /**
+ * Calculates a suggestion for the bounds which can be used as input for
+ * {@link BIT.SDS.WindowUtil.cascadeOverlap}.
+ *
+ * The suggestion is based on the current size of the browser window, therefore you should
+ * adjust the browser window to your needs before calling this method.
+ *
+ * @return {BIT.SDS.WindowUtil~Bounds} The suggested bounds.
+ *
+ * @example
+ * BIT.SDS.WindowUtil.suggestBounds();
+ * // => {x: 160, y: 139, width: 1640, height: 830}
+ */
+ suggestBounds: function() {
+ var bounds = {};
+
+ var TASKBAR_HEIGHT = Ext.get("sds-taskbar").getHeight();
+ var DESKTOP_SHORTCUTS_WIDTH = Ext.select("li.launch-icon").first().getWidth() + (2 * Ext.select("ul.sds-desktop-shortcut").first().getMargins("l"));
+
+ var MARGIN_TOP_SMALL = 10;
+ var MARGIN_RIGHT_SMALL = 10;
+ var MARGIN_BOTTOM_SMALL = 10;
+ var MARGIN_LEFT_SMALL = 10;
+
+ var MARGIN_TOP_LARGE = 100;
+ var MARGIN_RIGHT_LARGE = 100;
+ var MARGIN_BOTTOM_LARGE = 100;
+ var MARGIN_LEFT_LARGE = DESKTOP_SHORTCUTS_WIDTH - ((DESKTOP_SHORTCUTS_WIDTH - 11) % 30) + 29;
+
+ var innerWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
+ var innerHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
+
+ innerWidth -= 16;
+ innerHeight -= 18;
+
+ if ((innerWidth - MARGIN_LEFT_LARGE - MARGIN_RIGHT_LARGE > 1200) && (innerHeight - MARGIN_TOP_LARGE - MARGIN_BOTTOM_LARGE - TASKBAR_HEIGHT > 700)) {
+ bounds.x = MARGIN_LEFT_LARGE;
+ bounds.y = MARGIN_TOP_LARGE + TASKBAR_HEIGHT;
+ bounds.width = innerWidth - MARGIN_LEFT_LARGE - MARGIN_RIGHT_LARGE;
+ bounds.height = innerHeight - MARGIN_TOP_LARGE - MARGIN_BOTTOM_LARGE - TASKBAR_HEIGHT;
+ } else {
+ bounds.x = MARGIN_LEFT_SMALL;
+ bounds.y = MARGIN_TOP_SMALL + TASKBAR_HEIGHT;
+ bounds.width = innerWidth - MARGIN_LEFT_SMALL - MARGIN_RIGHT_SMALL;
+ bounds.height = innerHeight - MARGIN_TOP_SMALL - MARGIN_BOTTOM_SMALL - TASKBAR_HEIGHT;
+ }
+
+ bounds.width -= bounds.width % 5;
+ bounds.height -= bounds.height % 5;
+
+ return bounds;
+ }
+ }
+});