afterpresets 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/native-runtime/CMakeLists.txt +82 -0
- package/native-runtime/README.md +40 -0
- package/native-runtime/THIRD_PARTY_NOTICES.md +14 -0
- package/native-runtime/include/ap_native.h +223 -0
- package/native-runtime/src/apply.cpp +199 -0
- package/native-runtime/src/crypto.cpp +225 -0
- package/native-runtime/src/crypto.hpp +51 -0
- package/native-runtime/src/dialog.cpp +62 -0
- package/native-runtime/src/dialog.hpp +35 -0
- package/native-runtime/src/engine.cpp +909 -0
- package/native-runtime/src/engine.hpp +232 -0
- package/native-runtime/src/fsutil.cpp +291 -0
- package/native-runtime/src/fsutil.hpp +57 -0
- package/native-runtime/src/json.cpp +326 -0
- package/native-runtime/src/json.hpp +70 -0
- package/native-runtime/src/platform.hpp +83 -0
- package/native-runtime/src/platform_common.cpp +60 -0
- package/native-runtime/src/platform_mac.mm +314 -0
- package/native-runtime/src/platform_win.cpp +277 -0
- package/native-runtime/src/runtime.cpp +473 -0
- package/native-runtime/src/util.cpp +169 -0
- package/native-runtime/src/util.hpp +32 -0
- package/native-runtime/src/zip.cpp +243 -0
- package/native-runtime/src/zip.hpp +58 -0
- package/native-runtime/third_party/monocypher/LICENCE.md +167 -0
- package/native-runtime/third_party/monocypher/monocypher-ed25519.c +500 -0
- package/native-runtime/third_party/monocypher/monocypher-ed25519.h +140 -0
- package/native-runtime/third_party/monocypher/monocypher.c +2956 -0
- package/native-runtime/third_party/monocypher/monocypher.h +321 -0
- package/native-runtime/third_party/tinf/LICENSE +22 -0
- package/native-runtime/third_party/tinf/crc32.c +57 -0
- package/native-runtime/third_party/tinf/tinf.h +140 -0
- package/native-runtime/third_party/tinf/tinflate.c +636 -0
- package/native-runtime/vendor-files.json +39 -0
- package/package.json +6 -3
- package/runtime/docs/cep/host-after-effects.md +57 -0
- package/runtime/docs/cep/host-premiere.md +84 -0
- package/runtime/docs/{host.md → cep/host.md} +42 -16
- package/runtime/docs/{README.md → common/README.md} +10 -8
- package/runtime/docs/{assets.md → common/assets.md} +16 -2
- package/runtime/docs/{licensing.md → common/licensing.md} +1 -1
- package/runtime/docs/{panels.md → common/panels.md} +5 -5
- package/runtime/docs/{storage.md → common/storage.md} +5 -5
- package/runtime/docs/{updates.md → common/updates.md} +1 -1
- package/runtime/docs/native/README.md +44 -0
- package/runtime/docs/native/debugging.md +140 -0
- package/runtime/docs/native/rules.md +402 -0
- package/runtime/docs/native/testing.md +154 -0
- package/runtime/package.json +1 -1
- package/runtime/scripts/ae-sdk-docs/config.js +102 -0
- package/runtime/scripts/ae-sdk-docs/convert.js +430 -0
- package/runtime/scripts/ae-sdk-docs/index.js +314 -0
- package/runtime/scripts/ae-sdk-docs/source.js +205 -0
- package/runtime/scripts/ae-sdk-docs/split.js +170 -0
- package/runtime/scripts/ae-sdk-docs.js +72 -0
- package/runtime/src/host.js +1 -1
- package/runtime/src/updates.js +1 -1
- package/src/api/error-messages.js +1 -1
- package/src/build/build.js +7 -7
- package/src/build/html.js +1 -1
- package/src/build/jsx.js +2 -2
- package/src/build/runtime.js +3 -3
- package/src/check/check.js +3 -3
- package/src/check/native.js +14 -0
- package/src/check/pricing.js +1 -1
- package/src/check/types.js +2 -2
- package/src/cli.js +5 -5
- package/src/commands/add.js +8 -3
- package/src/commands/build.js +8 -2
- package/src/commands/check.js +11 -3
- package/src/commands/dev.js +9 -2
- package/src/commands/docs.js +5 -1
- package/src/commands/init.js +15 -3
- package/src/commands/push.js +5 -2
- package/src/commands/sdk.js +14 -0
- package/src/commands/testers.js +1 -1
- package/src/commands/upgrade.js +2 -2
- package/src/commands/whoami.js +1 -1
- package/src/config.js +1 -1
- package/src/dev/bridge.js +8 -0
- package/src/dev/cep-link.js +1 -1
- package/src/dev/debug-file.js +5 -5
- package/src/dev/native.js +3 -0
- package/src/dev/session.js +4 -3
- package/src/mcp/extendscript.js +10 -7
- package/src/mcp/host-target.js +1 -0
- package/src/mcp/panels.js +1 -1
- package/src/mcp/png.js +1 -0
- package/src/mcp/server.js +2 -2
- package/src/mcp/tools/capture.js +3 -0
- package/src/mcp/tools/host.js +1 -1
- package/src/mcp/tools/native.js +6 -0
- package/src/mcp/tools/panel.js +4 -4
- package/src/mcp/tools/project.js +47 -31
- package/src/mcp/tools/releases.js +23 -20
- package/src/native/build.js +27 -0
- package/src/native/cmake/CMakeLists.txt +88 -0
- package/src/native/cmake/zig-windows.cmake +19 -0
- package/src/native/effect.js +3 -0
- package/src/native/install.js +8 -0
- package/src/native/installers.js +4 -0
- package/src/native/pipl.js +20 -0
- package/src/native/restart.js +2 -0
- package/src/native/sdk.js +8 -0
- package/src/project/add-panel.js +1 -1
- package/src/project/docs.js +3 -1
- package/src/project/manifest.js +3 -2
- package/src/project/project.js +2 -2
- package/src/project/template.js +5 -1
- package/src/prompt.js +1 -1
- package/src/release/installers.js +4 -4
- package/src/release/native.js +3 -0
- package/src/release/package.js +1 -1
- package/src/release/publish.js +1 -1
- package/src/system/run-command.js +1 -1
- package/src/targets.js +1 -0
- package/src/user-config.js +2 -2
- package/template/cep/AGENTS.md +104 -0
- package/template/{README.md → cep/README.md} +16 -11
- package/template/cep/docs/code_guide.md +67 -0
- package/template/cep/docs/initial_setup.md +115 -0
- package/template/cep/docs/release_checklist.md +24 -0
- package/template/{src → cep/src}/features/home/home.html +4 -4
- package/template/{src → cep/src}/features/home/home.js +10 -11
- package/template/{src → cep/src}/shared/lib/host-theme.js +3 -3
- package/template/{src → cep/src}/shared/lib/view.js +1 -1
- package/template/{src → cep/src}/shared/ui/tokens.css +1 -1
- package/template/hosts/AGENTS.two-apps.md +12 -0
- package/template/hosts/aeft/AGENTS.host.md +10 -0
- package/template/hosts/aeft/jsx/aeft/home.jsx +16 -0
- package/template/hosts/ppro/AGENTS.host.md +15 -0
- package/template/hosts/ppro/jsx/ppro/home.jsx +20 -0
- package/template/native/.mcp.json +8 -0
- package/template/native/AGENTS.md +105 -0
- package/template/native/README.md +71 -0
- package/template/native/docs/code_guide.md +72 -0
- package/template/native/docs/initial_setup.md +179 -0
- package/template/native/docs/release_checklist.md +24 -0
- package/template/native/effect.json +16 -0
- package/template/native/gitignore +8 -0
- package/template/native/package.json +14 -0
- package/template/native/src/Effect.cpp +224 -0
- package/template/native/src/License.cpp +95 -0
- package/template/native/src/License.h +29 -0
- package/template/native/src/Render.h +68 -0
- package/vendor/afterpresets-additions/AfterEffects.d.ts +291 -0
- package/vendor/afterpresets-additions/Premiere.d.ts +179 -0
- package/vendor/types-for-adobe/Premiere/24.0/index.d.ts +2567 -0
- package/vendor/types-for-adobe/README.md +8 -4
- package/template/AGENTS.md +0 -168
- package/template/docs/initial_setup.md +0 -69
- package/template/jsx/home.jsx +0 -17
- /package/runtime/docs/{boot.md → common/boot.md} +0 -0
- /package/template/{.mcp.json → cep/.mcp.json} +0 -0
- /package/template/{gitignore → cep/gitignore} +0 -0
- /package/template/{package.json → cep/package.json} +0 -0
- /package/template/{src → cep/src}/features/home/home.css +0 -0
- /package/template/{src → cep/src}/features/license-screen/license-screen.css +0 -0
- /package/template/{src → cep/src}/features/license-screen/license-screen.html +0 -0
- /package/template/{src → cep/src}/features/license-screen/license-screen.js +0 -0
- /package/template/{src → cep/src}/features/update-screen/update-screen.css +0 -0
- /package/template/{src → cep/src}/features/update-screen/update-screen.html +0 -0
- /package/template/{src → cep/src}/features/update-screen/update-screen.js +0 -0
- /package/template/{src → cep/src}/panels/main/index.html +0 -0
- /package/template/{src → cep/src}/panels/main/main.js +0 -0
- /package/template/{src → cep/src}/shared/lib/dom.js +0 -0
- /package/template/{src → cep/src}/shared/lib/markdown.js +0 -0
- /package/template/{src → cep/src}/shared/ui/base.css +0 -0
- /package/template/{src → cep/src}/shared/ui/button.css +0 -0
- /package/template/{src → cep/src}/shared/ui/card.css +0 -0
- /package/template/{src → cep/src}/shared/ui/field.css +0 -0
- /package/template/{src → cep/src}/shared/ui/index.css +0 -0
package/README.md
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
# AfterPresets CLI
|
|
2
2
|
|
|
3
|
-
Make Adobe After Effects extensions with an AI agent, and sell them on
|
|
3
|
+
Make Adobe After Effects and Premiere Pro extensions with an AI agent, and sell them on
|
|
4
4
|
[AfterPresets](https://afterpresets.com) with licensing, signed auto-updates
|
|
5
5
|
and checkout handled.
|
|
6
6
|
|
|
7
7
|
You do not need to write code. Open an empty folder in your agent (the Code
|
|
8
8
|
tab of the Claude desktop app, Claude Code, Codex, Cursor...) and follow the
|
|
9
|
-
**[Quickstart](https://docs.afterpresets.com/
|
|
9
|
+
**[Quickstart](https://docs.afterpresets.com/extensions/quickstart/)**.
|
|
10
|
+
Building a native After Effects effect plugin (C++) instead? See the
|
|
11
|
+
**[plugin Quickstart](https://docs.afterpresets.com/plugins/quickstart/)**.
|
|
10
12
|
|
|
11
13
|
```sh
|
|
12
14
|
npx afterpresets login
|
|
13
15
|
npx afterpresets init my-extension
|
|
14
16
|
```
|
|
15
17
|
|
|
16
|
-
Inside a project, `ap dev` puts the extension into After Effects
|
|
17
|
-
it on every save, `ap push` publishes a release, and `ap upgrade` updates
|
|
18
|
+
Inside a project, `ap dev` puts the extension into After Effects or Premiere
|
|
19
|
+
Pro and reloads it on every save, `ap push` publishes a release, and `ap upgrade` updates
|
|
18
20
|
this package. Every project includes `AGENTS.md` and an MCP server
|
|
19
21
|
(`ap mcp`) so your agent can see, click and test the live panel.
|
|
20
22
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.20)
|
|
2
|
+
project(ap_native VERSION 0.1.0 LANGUAGES C CXX)
|
|
3
|
+
|
|
4
|
+
# Plugins add this folder with add_subdirectory() and link ap_native statically.
|
|
5
|
+
# Tests: tests/run.sh (configures this file with -DAP_NATIVE_TESTS=ON).
|
|
6
|
+
option(AP_NATIVE_TESTS "Build the host-free unit tests" OFF)
|
|
7
|
+
|
|
8
|
+
if(APPLE)
|
|
9
|
+
enable_language(OBJCXX)
|
|
10
|
+
if(NOT CMAKE_OSX_DEPLOYMENT_TARGET)
|
|
11
|
+
set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0" CACHE STRING "" FORCE)
|
|
12
|
+
endif()
|
|
13
|
+
endif()
|
|
14
|
+
|
|
15
|
+
set(AP_NATIVE_SOURCES
|
|
16
|
+
src/apply.cpp
|
|
17
|
+
src/crypto.cpp
|
|
18
|
+
src/dialog.cpp
|
|
19
|
+
src/engine.cpp
|
|
20
|
+
src/fsutil.cpp
|
|
21
|
+
src/json.cpp
|
|
22
|
+
src/platform_common.cpp
|
|
23
|
+
src/runtime.cpp
|
|
24
|
+
src/util.cpp
|
|
25
|
+
src/zip.cpp
|
|
26
|
+
third_party/monocypher/monocypher.c
|
|
27
|
+
third_party/monocypher/monocypher-ed25519.c
|
|
28
|
+
third_party/tinf/tinflate.c
|
|
29
|
+
third_party/tinf/crc32.c
|
|
30
|
+
)
|
|
31
|
+
if(APPLE)
|
|
32
|
+
list(APPEND AP_NATIVE_SOURCES src/platform_mac.mm)
|
|
33
|
+
elseif(WIN32)
|
|
34
|
+
list(APPEND AP_NATIVE_SOURCES src/platform_win.cpp)
|
|
35
|
+
else()
|
|
36
|
+
message(FATAL_ERROR "ap_native supports macOS and Windows only")
|
|
37
|
+
endif()
|
|
38
|
+
|
|
39
|
+
add_library(ap_native STATIC ${AP_NATIVE_SOURCES})
|
|
40
|
+
target_include_directories(ap_native
|
|
41
|
+
PUBLIC include
|
|
42
|
+
PRIVATE src third_party/monocypher third_party/tinf)
|
|
43
|
+
target_compile_features(ap_native PUBLIC cxx_std_17)
|
|
44
|
+
# Hidden symbols: two plugins carrying different copies never clash, and the
|
|
45
|
+
# shipped binary does not advertise the license functions by name.
|
|
46
|
+
set_target_properties(ap_native PROPERTIES
|
|
47
|
+
CXX_VISIBILITY_PRESET hidden
|
|
48
|
+
C_VISIBILITY_PRESET hidden
|
|
49
|
+
OBJCXX_VISIBILITY_PRESET hidden
|
|
50
|
+
VISIBILITY_INLINES_HIDDEN ON
|
|
51
|
+
POSITION_INDEPENDENT_CODE ON)
|
|
52
|
+
|
|
53
|
+
# Warnings for our own sources only (not the vendored C files).
|
|
54
|
+
set_source_files_properties(
|
|
55
|
+
src/apply.cpp src/crypto.cpp src/dialog.cpp src/engine.cpp src/fsutil.cpp src/json.cpp
|
|
56
|
+
src/platform_common.cpp src/runtime.cpp src/util.cpp src/zip.cpp src/platform_win.cpp
|
|
57
|
+
PROPERTIES COMPILE_OPTIONS "-Wall;-Wextra;-Wno-unused-parameter")
|
|
58
|
+
|
|
59
|
+
if(APPLE)
|
|
60
|
+
set_source_files_properties(src/platform_mac.mm PROPERTIES COMPILE_FLAGS "-fobjc-arc -Wall -Wextra -Wno-unused-parameter")
|
|
61
|
+
target_link_libraries(ap_native PUBLIC "-framework Foundation" "-framework AppKit" "-framework IOKit")
|
|
62
|
+
elseif(WIN32)
|
|
63
|
+
target_compile_definitions(ap_native PRIVATE UNICODE _UNICODE WIN32_LEAN_AND_MEAN)
|
|
64
|
+
target_link_libraries(ap_native PUBLIC winhttp iphlpapi bcrypt shell32 ole32 advapi32 user32)
|
|
65
|
+
endif()
|
|
66
|
+
|
|
67
|
+
if(AP_NATIVE_TESTS)
|
|
68
|
+
add_executable(ap_native_tests
|
|
69
|
+
tests/test_main.cpp
|
|
70
|
+
tests/test_crypto.cpp
|
|
71
|
+
tests/test_json_zip.cpp
|
|
72
|
+
tests/test_license.cpp
|
|
73
|
+
tests/test_updates.cpp
|
|
74
|
+
tests/test_runtime.cpp
|
|
75
|
+
tests/test_server.cpp
|
|
76
|
+
tests/test_dialog.cpp)
|
|
77
|
+
target_include_directories(ap_native_tests PRIVATE src tests third_party/monocypher third_party/tinf)
|
|
78
|
+
target_link_libraries(ap_native_tests PRIVATE ap_native)
|
|
79
|
+
add_executable(ap_native_probe tests/probe.cpp)
|
|
80
|
+
target_include_directories(ap_native_probe PRIVATE src)
|
|
81
|
+
target_link_libraries(ap_native_probe PRIVATE ap_native)
|
|
82
|
+
endif()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# ap_native
|
|
2
|
+
|
|
3
|
+
The AfterPresets runtime for native (C++) After Effects plugins: licensing
|
|
4
|
+
(browser sign-in, signed license proofs, offline grace) and signed self-updates.
|
|
5
|
+
It is the C++ counterpart of `@afterpresets/runtime` and talks to the same API.
|
|
6
|
+
AfterPresets maintains it; developers do not edit it. The CLI copies the files
|
|
7
|
+
in `vendor-files.json` into each native project.
|
|
8
|
+
|
|
9
|
+
- Design, threading, security and the integration snippet:
|
|
10
|
+
[`docs/native-runtime-design.md`](../../docs/native-runtime-design.md)
|
|
11
|
+
- C API: [`include/ap_native.h`](include/ap_native.h)
|
|
12
|
+
- Third-party code: [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md)
|
|
13
|
+
|
|
14
|
+
## Use from a plugin's CMake
|
|
15
|
+
|
|
16
|
+
```cmake
|
|
17
|
+
add_subdirectory(vendor/ap_native)
|
|
18
|
+
target_link_libraries(MyEffect PRIVATE ap_native)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Tests (maintainers)
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
packages/native-runtime/tests/run.sh # 63 C++ tests + Node parity/integration tests
|
|
25
|
+
AP_SANITIZE=1 packages/native-runtime/tests/run.sh # AddressSanitizer + UBSan
|
|
26
|
+
AP_TSAN=1 packages/native-runtime/tests/run.sh # ThreadSanitizer
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Requirements: macOS with the Command Line Tools, `brew install cmake ninja`,
|
|
30
|
+
Node 18+, and `pnpm install` at the repo root. The server-signature tests run
|
|
31
|
+
the API's signing code through `apps/api`'s `tsx`.
|
|
32
|
+
|
|
33
|
+
Windows cross-build check (needs `brew install zig`; links, but cannot run without Windows):
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
cmake -S packages/native-runtime -B packages/native-runtime/build/win -G Ninja \
|
|
37
|
+
-DAP_NATIVE_TESTS=ON \
|
|
38
|
+
-DCMAKE_TOOLCHAIN_FILE=docs/native-research/spike-hello/cmake/zig-x86_64-windows.cmake
|
|
39
|
+
ninja -C packages/native-runtime/build/win
|
|
40
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Third-party code in ap_native
|
|
2
|
+
|
|
3
|
+
Both are vendored unmodified. Their licences allow use in closed-source,
|
|
4
|
+
commercial plugins. Keep this file, and each folder's licence file, with every
|
|
5
|
+
copy of the library.
|
|
6
|
+
|
|
7
|
+
| Folder | Project | Version | Licence | Used for |
|
|
8
|
+
|---|---|---|---|---|
|
|
9
|
+
| `third_party/monocypher/` | [Monocypher](https://monocypher.org) (`monocypher.c/.h`, `optional/monocypher-ed25519.c/.h`) | 4.0.2 (matches the GitHub tag apart from the version comment) | CC0-1.0 or BSD-2-Clause, your choice (`LICENCE.md`) | Ed25519 signature verification (SHA-512 inside) |
|
|
10
|
+
| `third_party/tinf/` | [tinf](https://github.com/jibsen/tinf) by Jørgen Ibsen (`tinflate.c`, `crc32.c`, `tinf.h`) | commit 57ffa1f (2022-02-12) | zlib (`LICENSE`) | Inflate (ZIP deflate entries) and CRC-32 |
|
|
11
|
+
|
|
12
|
+
The CC0 option means Monocypher needs no attribution. tinf's zlib licence asks
|
|
13
|
+
that its notice stay in the source distribution; attribution in shipped
|
|
14
|
+
binaries is appreciated but not required.
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/* ap_native - AfterPresets runtime for native (C++) After Effects plugins.
|
|
2
|
+
*
|
|
3
|
+
* Licensing (browser sign-in, Ed25519-signed license proofs, offline grace)
|
|
4
|
+
* and signed updates (check, download, verify, stage, apply on next launch)
|
|
5
|
+
* against the same API as the JS runtime (@afterpresets/runtime):
|
|
6
|
+
* POST {apiBase}/v1/api/extensions/{bundleId}/license/sign-in | sign-in/poll
|
|
7
|
+
* | validate | sign-out, and /updates/check.
|
|
8
|
+
* Design: docs/native-runtime-design.md.
|
|
9
|
+
*
|
|
10
|
+
* Threading contract
|
|
11
|
+
* - ap_init / ap_shutdown / ap_apply_staged_update: main (UI) thread, from
|
|
12
|
+
* PF_Cmd_GLOBAL_SETUP / PF_Cmd_GLOBAL_SETDOWN.
|
|
13
|
+
* - ap_is_unlocked, ap_status_get, ap_generation: any thread, including
|
|
14
|
+
* render threads (MFR). Lock-free atomic reads; never block, never allocate.
|
|
15
|
+
* - Everything else: any non-render thread (normally the UI thread).
|
|
16
|
+
* Commands only enqueue work; all network I/O happens on one background
|
|
17
|
+
* worker thread owned by the runtime. No call in this header ever does
|
|
18
|
+
* network I/O on the calling thread.
|
|
19
|
+
*
|
|
20
|
+
* The plugin links this library statically. Build it with hidden symbol
|
|
21
|
+
* visibility (the CMake target does) so two plugins carrying different copies
|
|
22
|
+
* never clash. No secrets live in this library: the public key verifies,
|
|
23
|
+
* it cannot sign. */
|
|
24
|
+
#ifndef AP_NATIVE_H
|
|
25
|
+
#define AP_NATIVE_H
|
|
26
|
+
|
|
27
|
+
#include <stddef.h>
|
|
28
|
+
#include <stdint.h>
|
|
29
|
+
|
|
30
|
+
#ifdef __cplusplus
|
|
31
|
+
extern "C" {
|
|
32
|
+
#endif
|
|
33
|
+
|
|
34
|
+
#define AP_NATIVE_VERSION "0.1.0"
|
|
35
|
+
|
|
36
|
+
typedef struct ap_runtime ap_runtime;
|
|
37
|
+
|
|
38
|
+
/* Same statuses as the JS runtime's AP.license.state.status. */
|
|
39
|
+
typedef enum ap_status {
|
|
40
|
+
AP_STATUS_DISABLED = 0, /* licensing off: always unlocked */
|
|
41
|
+
AP_STATUS_SIGNED_OUT,
|
|
42
|
+
AP_STATUS_SIGNING_IN, /* browser sign-in in progress (state.sign_in_url) */
|
|
43
|
+
AP_STATUS_CHECKING,
|
|
44
|
+
AP_STATUS_ACTIVE,
|
|
45
|
+
AP_STATUS_NOT_OWNED,
|
|
46
|
+
AP_STATUS_REFUNDED,
|
|
47
|
+
AP_STATUS_OFFLINE_GRACE, /* unlocked; state.grace_days_left */
|
|
48
|
+
AP_STATUS_OFFLINE_EXPIRED,
|
|
49
|
+
AP_STATUS_ERROR
|
|
50
|
+
} ap_status;
|
|
51
|
+
|
|
52
|
+
typedef enum ap_update_mode {
|
|
53
|
+
AP_UPDATES_OFF = 0, /* never checks */
|
|
54
|
+
AP_UPDATES_MANUAL, /* only when ap_check_for_update is called */
|
|
55
|
+
AP_UPDATES_NOTIFY, /* checks automatically; downloads on ap_download_update */
|
|
56
|
+
AP_UPDATES_BACKGROUND /* checks, downloads, verifies and stages automatically (default) */
|
|
57
|
+
} ap_update_mode;
|
|
58
|
+
|
|
59
|
+
typedef enum ap_update_status {
|
|
60
|
+
AP_UPDATE_IDLE = 0,
|
|
61
|
+
AP_UPDATE_CHECKING,
|
|
62
|
+
AP_UPDATE_UP_TO_DATE,
|
|
63
|
+
AP_UPDATE_AVAILABLE, /* found, not downloaded (notify/manual mode) */
|
|
64
|
+
AP_UPDATE_DOWNLOADING,
|
|
65
|
+
AP_UPDATE_STAGED, /* verified and staged; applied by ap_apply_staged_update */
|
|
66
|
+
AP_UPDATE_APPLIED, /* files replaced; the new version runs after After Effects restarts */
|
|
67
|
+
AP_UPDATE_ERROR
|
|
68
|
+
} ap_update_status;
|
|
69
|
+
|
|
70
|
+
typedef struct ap_config {
|
|
71
|
+
const char *bundle_id; /* com.afterpresets.<seller>.<slug> (required) */
|
|
72
|
+
const char *version; /* semver of this build (required) */
|
|
73
|
+
const char *api_base; /* https://api.afterpresets.com (http only for 127.0.0.1/localhost) */
|
|
74
|
+
const char *public_key; /* Ed25519 public key: base64 SPKI DER, PEM, base64 of a PEM, or base64 of the raw 32 bytes */
|
|
75
|
+
const char *product_url; /* listing page (buy / download again) */
|
|
76
|
+
const char *manage_devices_url; /* where buyers free a computer slot */
|
|
77
|
+
int license_enabled; /* 0: status DISABLED, no license network calls */
|
|
78
|
+
int offline_grace_days; /* 0-30, measured from the last signed validation */
|
|
79
|
+
int check_in_background; /* nonzero: a signed but stale check unlocks at launch while the API is asked */
|
|
80
|
+
ap_update_mode update_mode;
|
|
81
|
+
const char *channel; /* "production" (default) or "test"; only validated by the API */
|
|
82
|
+
int dev; /* dev build: updates are checked but never staged or applied */
|
|
83
|
+
int apply_when_staged; /* default 1: replace the installed plugin right after a verified download, so the
|
|
84
|
+
effect can show "Update installed: restart After Effects" in this session.
|
|
85
|
+
0: only ap_apply_staged_update replaces it (GLOBAL_SETDOWN / next GLOBAL_SETUP). */
|
|
86
|
+
const char *install_dir; /* optional: the installed <bundleId> folder. NULL: found from the binary's path */
|
|
87
|
+
const char *data_root; /* optional (tests): replaces ~/Library/Application Support or %LOCALAPPDATA%; logs then go to <data_root>/AfterPresets/Logs */
|
|
88
|
+
const char *build_id; /* optional; written to the log at ap_init so tools can prove which build is loaded */
|
|
89
|
+
void (*on_change)(void *user); /* optional; called on the WORKER thread after any state change. Only set a flag. */
|
|
90
|
+
void (*log)(void *user, int level, const char *message); /* optional; 0 debug 1 info 2 warn 3 error. Never receives tokens or signed URLs. */
|
|
91
|
+
void *user;
|
|
92
|
+
} ap_config;
|
|
93
|
+
|
|
94
|
+
#define AP_TEXT_MAX 512
|
|
95
|
+
|
|
96
|
+
typedef struct ap_state {
|
|
97
|
+
ap_status status;
|
|
98
|
+
int unlocked; /* active, offline_grace or disabled */
|
|
99
|
+
int grace_days_left;
|
|
100
|
+
uint64_t generation; /* increments on every license or update state change */
|
|
101
|
+
char message[AP_TEXT_MAX]; /* default English message for the status (UTF-8) */
|
|
102
|
+
char sign_in_url[AP_TEXT_MAX]; /* while SIGNING_IN: the page to reopen */
|
|
103
|
+
char username[128]; /* signed-in AfterPresets username, if known */
|
|
104
|
+
} ap_state;
|
|
105
|
+
|
|
106
|
+
typedef struct ap_update_state {
|
|
107
|
+
ap_update_status status;
|
|
108
|
+
int restart_required; /* 1 after a successful apply */
|
|
109
|
+
int needs_manual_install; /* staged but the install folder is not writable */
|
|
110
|
+
char version[64]; /* target version */
|
|
111
|
+
char direction[16]; /* "upgrade" | "downgrade" */
|
|
112
|
+
char change_log[2048]; /* target release's changelog, truncated */
|
|
113
|
+
char error_code[48]; /* e.g. network, bad_signature, bad_checksum, bad_zip, not_writable */
|
|
114
|
+
char error_message[AP_TEXT_MAX];
|
|
115
|
+
} ap_update_state;
|
|
116
|
+
|
|
117
|
+
typedef enum ap_apply_result {
|
|
118
|
+
AP_APPLY_NOTHING = 0, /* nothing staged */
|
|
119
|
+
AP_APPLY_OK = 1, /* replaced; restart After Effects to run it */
|
|
120
|
+
AP_APPLY_NOT_INSTALLED = -1, /* the binary is not inside an installed <bundleId> folder (dev build, symlink) */
|
|
121
|
+
AP_APPLY_NOT_WRITABLE = -2, /* install folder not writable by this user: buyer must reinstall */
|
|
122
|
+
AP_APPLY_FAILED = -3, /* error while replacing; the previous version was restored */
|
|
123
|
+
AP_APPLY_DEV = -4 /* dev build */
|
|
124
|
+
} ap_apply_result;
|
|
125
|
+
|
|
126
|
+
/* Fills defaults: licensing on, 0 grace days, check_in_background on,
|
|
127
|
+
* AP_UPDATES_BACKGROUND, apply_when_staged on, channel "production", everything else NULL/0. */
|
|
128
|
+
void ap_config_defaults(ap_config *config);
|
|
129
|
+
|
|
130
|
+
/* Creates the runtime: reads the license cache from disk and verifies its
|
|
131
|
+
* proof synchronously (no network; a few ms), then starts the worker thread,
|
|
132
|
+
* which validates online when needed and checks for updates. Returns NULL for
|
|
133
|
+
* an invalid config (bad bundle id, version or api_base). Call once per
|
|
134
|
+
* process (PF_Cmd_GLOBAL_SETUP); keep the pointer in global data. */
|
|
135
|
+
ap_runtime *ap_init(const ap_config *config);
|
|
136
|
+
|
|
137
|
+
/* Stops the worker (cancels in-flight requests; waits at most ~3 s) and frees
|
|
138
|
+
* the runtime. PF_Cmd_GLOBAL_SETDOWN. NULL is ignored. */
|
|
139
|
+
void ap_shutdown(ap_runtime *rt);
|
|
140
|
+
|
|
141
|
+
/* Render-thread safe. 1 when the effect may render without the watermark. */
|
|
142
|
+
int ap_is_unlocked(const ap_runtime *rt);
|
|
143
|
+
ap_status ap_status_get(const ap_runtime *rt);
|
|
144
|
+
/* Changes whenever anything in ap_state or ap_update_state changes. Cheap:
|
|
145
|
+
* compare it in PF_Cmd_UPDATE_PARAMS_UI and refresh the UI when it moved. */
|
|
146
|
+
uint64_t ap_generation(const ap_runtime *rt);
|
|
147
|
+
|
|
148
|
+
void ap_get_state(ap_runtime *rt, ap_state *out);
|
|
149
|
+
void ap_get_update_state(ap_runtime *rt, ap_update_state *out);
|
|
150
|
+
|
|
151
|
+
/* Starts a browser sign-in: asks the API for a sign-in request, opens the
|
|
152
|
+
* verification page in the default browser and polls until the buyer approves
|
|
153
|
+
* it (status SIGNING_IN, then ACTIVE). Returns 0 when queued, -1 when
|
|
154
|
+
* licensing is off. Safe to call repeatedly (one sign-in at a time). */
|
|
155
|
+
int ap_begin_sign_in(ap_runtime *rt);
|
|
156
|
+
void ap_cancel_sign_in(ap_runtime *rt);
|
|
157
|
+
/* Reopens the pending sign-in page (e.g. the buyer closed the tab). */
|
|
158
|
+
void ap_open_sign_in_page(ap_runtime *rt);
|
|
159
|
+
/* Re-validates the stored license (after NOT_OWNED, OFFLINE_EXPIRED, ERROR). */
|
|
160
|
+
void ap_retry(ap_runtime *rt);
|
|
161
|
+
/* Deletes the local license and frees this computer's slot (best effort when offline). */
|
|
162
|
+
void ap_sign_out(ap_runtime *rt);
|
|
163
|
+
|
|
164
|
+
/* Checks now (any mode except OFF). In BACKGROUND mode a found update is downloaded and staged. */
|
|
165
|
+
void ap_check_for_update(ap_runtime *rt);
|
|
166
|
+
/* Downloads, verifies and stages the update found by the last check (NOTIFY/MANUAL modes). */
|
|
167
|
+
void ap_download_update(ap_runtime *rt);
|
|
168
|
+
/* One line for the effect's UI (UTF-8), or "" when there is nothing to say:
|
|
169
|
+
* "Update installed: restart After Effects to use v1.2.0"
|
|
170
|
+
* "Update v1.2.0 downloaded: it installs when After Effects quits"
|
|
171
|
+
* "Update v1.2.0 is ready, but this plugin's folder is not writable: download it again from AfterPresets"
|
|
172
|
+
* "Update v1.2.0 available"
|
|
173
|
+
* Copies into buf (NUL-terminated, never cut inside a UTF-8 sequence); returns the full length. */
|
|
174
|
+
size_t ap_update_notice(ap_runtime *rt, char *buf, size_t size);
|
|
175
|
+
/* The update's changelog (up to 8000 bytes, UTF-8) for the Options dialog; returns the full length. */
|
|
176
|
+
size_t ap_update_changelog(ap_runtime *rt, char *buf, size_t size);
|
|
177
|
+
|
|
178
|
+
typedef enum ap_dialog_action {
|
|
179
|
+
AP_DIALOG_NONE = -1, /* could not be shown (not the main thread) */
|
|
180
|
+
AP_DIALOG_CLOSE = 0,
|
|
181
|
+
AP_DIALOG_SIGN_IN, /* browser sign-in started */
|
|
182
|
+
AP_DIALOG_OPEN_SIGN_IN_PAGE,
|
|
183
|
+
AP_DIALOG_CANCEL_SIGN_IN,
|
|
184
|
+
AP_DIALOG_SIGN_OUT,
|
|
185
|
+
AP_DIALOG_RETRY,
|
|
186
|
+
AP_DIALOG_OPEN_PRODUCT, /* product_url opened (buy) */
|
|
187
|
+
AP_DIALOG_MANAGE_DEVICES /* manage_devices_url opened */
|
|
188
|
+
} ap_dialog_action;
|
|
189
|
+
|
|
190
|
+
/* The effect's Options dialog (PF_OutFlag_I_DO_DIALOG -> PF_Cmd_DO_DIALOG).
|
|
191
|
+
* Shows the license status, the signed-in account, the update notice and
|
|
192
|
+
* changelog, with buttons for the current state (Sign in / Buy / Open
|
|
193
|
+
* sign-in page / Sign out / Try again / Manage devices), performs the chosen
|
|
194
|
+
* action (network work goes to the worker) and returns it. Modal; main thread
|
|
195
|
+
* only. title: the effect's name (UTF-8). */
|
|
196
|
+
ap_dialog_action ap_options_dialog(ap_runtime *rt, const char *title);
|
|
197
|
+
|
|
198
|
+
/* Replaces the installed files with the staged, verified update. Filesystem
|
|
199
|
+
* only, synchronous; call from PF_Cmd_GLOBAL_SETDOWN (After Effects is
|
|
200
|
+
* quitting) and/or PF_Cmd_GLOBAL_SETUP. The running code keeps running; the new
|
|
201
|
+
* version loads at the next launch. Never call from a render thread. */
|
|
202
|
+
ap_apply_result ap_apply_staged_update(ap_runtime *rt);
|
|
203
|
+
|
|
204
|
+
/* Appends a line to this plugin's log (macOS ~/Library/Logs/AfterPresets/<bundleId>.log,
|
|
205
|
+
* Windows %LOCALAPPDATA%\AfterPresets\Logs\<bundleId>.log; 1 MB, one rotated
|
|
206
|
+
* file). Any thread, including render threads: never blocks, never does I/O on
|
|
207
|
+
* the caller (the worker writes the file); a line is dropped if the queue is
|
|
208
|
+
* busy or full. Never log tokens or URLs; tokens and query strings are scrubbed anyway. */
|
|
209
|
+
void ap_log(ap_runtime *rt, int level, const char *message);
|
|
210
|
+
/* Copies the log file path into buf (NUL-terminated); returns its full length. */
|
|
211
|
+
size_t ap_log_path(ap_runtime *rt, char *buf, size_t size);
|
|
212
|
+
|
|
213
|
+
/* Waits until the worker has no queued or running work (tests, tools). 0 on idle, -1 on timeout. */
|
|
214
|
+
int ap_wait_idle(ap_runtime *rt, int timeout_ms);
|
|
215
|
+
|
|
216
|
+
const char *ap_status_name(ap_status status); /* "active", "signed_out", ... (JS names) */
|
|
217
|
+
const char *ap_update_status_name(ap_update_status status);
|
|
218
|
+
|
|
219
|
+
#ifdef __cplusplus
|
|
220
|
+
}
|
|
221
|
+
#endif
|
|
222
|
+
|
|
223
|
+
#endif /* AP_NATIVE_H */
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// Applies a staged, verified update to the installed <bundleId> folder.
|
|
2
|
+
//
|
|
3
|
+
// Install layout (docs/native-runtime-design.md, "Install layout"):
|
|
4
|
+
// .../MediaCore/AfterPresets/<bundleId>/<Name>.plugin (macOS)
|
|
5
|
+
// ...\MediaCore\AfterPresets\<bundleId>\<Name>.aex (Windows)
|
|
6
|
+
// The buyer's install script creates MediaCore/AfterPresets once with admin and
|
|
7
|
+
// gives it to the user, so updates never need admin. Only this OS's plugin is
|
|
8
|
+
// replaced; other files in <bundleId>/ are left alone, and any other plugin of
|
|
9
|
+
// this OS there (an old name after a rename) is removed so AE never loads two.
|
|
10
|
+
// macOS swaps the bundle atomically (renamex_np RENAME_SWAP); elsewhere the old
|
|
11
|
+
// plugin is renamed away first (Windows allows renaming a loaded DLL, not
|
|
12
|
+
// deleting or overwriting it). Work copies are named "<Name>.plugin.ap-new" /
|
|
13
|
+
// "<Name>.aex.ap-old" inside "(ap-update)" / "(ap-old)": AE only loads
|
|
14
|
+
// *.plugin / *.aex, so an interrupted apply never leaves a second loadable
|
|
15
|
+
// copy. The running process keeps its already-loaded code; the new version
|
|
16
|
+
// loads at the next launch.
|
|
17
|
+
#include <cctype>
|
|
18
|
+
#include <set>
|
|
19
|
+
|
|
20
|
+
#include "engine.hpp"
|
|
21
|
+
#include "fsutil.hpp"
|
|
22
|
+
#include "util.hpp"
|
|
23
|
+
|
|
24
|
+
namespace apn {
|
|
25
|
+
|
|
26
|
+
namespace {
|
|
27
|
+
|
|
28
|
+
const char *const WORK_DIR = "(ap-update)";
|
|
29
|
+
const char *const OLD_DIR = "(ap-old)";
|
|
30
|
+
|
|
31
|
+
struct Lock {
|
|
32
|
+
std::string path;
|
|
33
|
+
bool held = false;
|
|
34
|
+
Lock(const std::string &p, int64_t now) : path(p) {
|
|
35
|
+
for (int attempt = 0; attempt < 2 && !held; attempt++) {
|
|
36
|
+
auto r = fsu::write_exclusive(path, std::to_string(now));
|
|
37
|
+
if (r == fsu::Excl::Ok) { held = true; break; }
|
|
38
|
+
if (r == fsu::Excl::Error) break;
|
|
39
|
+
int64_t age = fsu::age_ms(path, now);
|
|
40
|
+
if (age >= 0 && age <= Engine::LOCK_STALE_MS) break;
|
|
41
|
+
fsu::remove_file(path);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
~Lock() {
|
|
45
|
+
if (held) fsu::remove_file(path);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
} // namespace
|
|
50
|
+
|
|
51
|
+
std::string find_install_dir(const std::string &binary_path, const std::string &bundle_id) {
|
|
52
|
+
if (binary_path.empty() || bundle_id.empty()) return "";
|
|
53
|
+
std::string dir = fsu::parent(binary_path);
|
|
54
|
+
for (int depth = 0; depth < 8 && !dir.empty(); depth++) {
|
|
55
|
+
if (fsu::basename(dir) == bundle_id) return dir;
|
|
56
|
+
std::string up = fsu::parent(dir);
|
|
57
|
+
if (up == dir) break;
|
|
58
|
+
dir = up;
|
|
59
|
+
}
|
|
60
|
+
return "";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
void cleanup_install_leftovers(const Settings &s) {
|
|
64
|
+
if (s.install_dir.empty() || fsu::basename(s.install_dir) != s.bundle_id) return;
|
|
65
|
+
fsu::remove_all(fsu::join(s.install_dir, OLD_DIR));
|
|
66
|
+
fsu::remove_all(fsu::join(s.install_dir, WORK_DIR));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
namespace {
|
|
70
|
+
|
|
71
|
+
bool is_os_plugin(const std::string &name, const std::string &os) {
|
|
72
|
+
std::string lower = name;
|
|
73
|
+
for (auto &c : lower) c = (char)std::tolower((unsigned char)c);
|
|
74
|
+
const std::string ext = os == "windows" ? ".aex" : ".plugin";
|
|
75
|
+
return lower.size() > ext.size() && lower.compare(lower.size() - ext.size(), ext.size(), ext) == 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
} // namespace
|
|
79
|
+
|
|
80
|
+
ApplyResult apply_staged_update(const Settings &s, const Env &env, std::string *version, std::string *error) {
|
|
81
|
+
auto say = [&](const std::string &m) {
|
|
82
|
+
if (error) *error = m;
|
|
83
|
+
if (env.log && !m.empty()) env.log(2, m);
|
|
84
|
+
};
|
|
85
|
+
Paths paths = Paths::make(env.data_root, s.bundle_id);
|
|
86
|
+
auto meta_text = fsu::read(paths.staged_meta, 1 << 20);
|
|
87
|
+
auto meta = meta_text ? Json::parse(*meta_text) : std::nullopt;
|
|
88
|
+
if (!meta || !meta->is_object() || !fsu::is_dir(paths.staged)) return ApplyResult::Nothing;
|
|
89
|
+
std::string staged_version = (*meta)["version"].str();
|
|
90
|
+
if (version) *version = staged_version;
|
|
91
|
+
if (s.dev) return ApplyResult::Dev;
|
|
92
|
+
if (staged_version.empty() || staged_version == s.version) {
|
|
93
|
+
fsu::remove_all(paths.staged);
|
|
94
|
+
fsu::remove_file(paths.staged_meta);
|
|
95
|
+
return ApplyResult::Nothing;
|
|
96
|
+
}
|
|
97
|
+
std::vector<std::string> staged_entries = fsu::list(paths.staged);
|
|
98
|
+
if (staged_entries.size() != 1 || !is_os_plugin(staged_entries[0], s.os)) {
|
|
99
|
+
fsu::remove_all(paths.staged);
|
|
100
|
+
fsu::remove_file(paths.staged_meta);
|
|
101
|
+
say("The staged update is incomplete; it was discarded.");
|
|
102
|
+
return ApplyResult::Failed;
|
|
103
|
+
}
|
|
104
|
+
const std::string plugin = staged_entries[0];
|
|
105
|
+
// Only ever touch a folder named exactly <bundleId>: a wrong install_dir
|
|
106
|
+
// must never make us rename things in the shared MediaCore folder.
|
|
107
|
+
const std::string &install = s.install_dir;
|
|
108
|
+
if (install.empty() || fsu::basename(install) != s.bundle_id || !fsu::is_dir(install)) {
|
|
109
|
+
say("The plugin is not inside an installed " + s.bundle_id + " folder; the update was not applied.");
|
|
110
|
+
return ApplyResult::NotInstalled;
|
|
111
|
+
}
|
|
112
|
+
if (!fsu::dir_writable(install)) {
|
|
113
|
+
say("The plugin folder is not writable; reinstall the plugin to update it.");
|
|
114
|
+
return ApplyResult::NotWritable;
|
|
115
|
+
}
|
|
116
|
+
Lock lock(paths.lock, env.now_ms());
|
|
117
|
+
if (!lock.held) {
|
|
118
|
+
say("Another process is installing this update.");
|
|
119
|
+
return ApplyResult::Failed;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
std::string work = fsu::join(install, WORK_DIR);
|
|
123
|
+
std::string old = fsu::join(install, OLD_DIR);
|
|
124
|
+
fsu::remove_all(work);
|
|
125
|
+
fsu::remove_all(old); // may fail on Windows while an old DLL is still loaded; names below are unique
|
|
126
|
+
std::string fresh = fsu::join(work, plugin + ".ap-new");
|
|
127
|
+
std::string copy_error;
|
|
128
|
+
bool copied = fsu::is_dir(fsu::join(paths.staged, plugin))
|
|
129
|
+
? fsu::copy_tree(fsu::join(paths.staged, plugin), fresh, ©_error)
|
|
130
|
+
: (fsu::mkdirs(work) && [&] {
|
|
131
|
+
auto bytes = fsu::read(fsu::join(paths.staged, plugin), 2ULL * 1024 * 1024 * 1024);
|
|
132
|
+
return bytes && fsu::write_file(fresh, *bytes, true);
|
|
133
|
+
}());
|
|
134
|
+
if (!copied) {
|
|
135
|
+
fsu::remove_all(work);
|
|
136
|
+
say("The update could not be copied: " + copy_error);
|
|
137
|
+
return ApplyResult::Failed;
|
|
138
|
+
}
|
|
139
|
+
// The staged files were verified; a quarantine flag must not block loading.
|
|
140
|
+
if (env.strip_quarantine) env.strip_quarantine(fresh);
|
|
141
|
+
if (!fsu::mkdirs(old)) {
|
|
142
|
+
fsu::remove_all(work);
|
|
143
|
+
say("The update could not be prepared.");
|
|
144
|
+
return ApplyResult::Failed;
|
|
145
|
+
}
|
|
146
|
+
std::string stamp = env.random_hex ? env.random_hex(4) : std::to_string(env.now_ms());
|
|
147
|
+
auto park_path = [&](const std::string &name) { return fsu::join(old, name + ".ap-old-" + stamp); };
|
|
148
|
+
|
|
149
|
+
std::string target = fsu::join(install, plugin);
|
|
150
|
+
std::string parked; // where the previous version went ("" if there was none)
|
|
151
|
+
bool failed = false;
|
|
152
|
+
if (!fsu::exists(target)) {
|
|
153
|
+
failed = !fsu::rename(fresh, target);
|
|
154
|
+
} else if (fsu::swap(fresh, target)) {
|
|
155
|
+
parked = park_path(plugin);
|
|
156
|
+
if (!fsu::rename(fresh, parked)) parked = fresh; // fresh now holds the previous version
|
|
157
|
+
} else {
|
|
158
|
+
parked = park_path(plugin);
|
|
159
|
+
if (!fsu::rename(target, parked)) {
|
|
160
|
+
parked.clear();
|
|
161
|
+
failed = true;
|
|
162
|
+
} else if (!fsu::rename(fresh, target)) {
|
|
163
|
+
fsu::rename(parked, target);
|
|
164
|
+
parked.clear();
|
|
165
|
+
failed = true;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Other plugins of this OS in the folder (the product's previous name): park them.
|
|
169
|
+
std::vector<std::pair<std::string, std::string>> removed;
|
|
170
|
+
if (!failed) {
|
|
171
|
+
for (const auto &name : fsu::list(install)) {
|
|
172
|
+
if (name == plugin || name == WORK_DIR || name == OLD_DIR || !is_os_plugin(name, s.os)) continue;
|
|
173
|
+
std::string to = park_path(name);
|
|
174
|
+
if (!fsu::rename(fsu::join(install, name), to)) { failed = true; break; }
|
|
175
|
+
removed.emplace_back(name, to);
|
|
176
|
+
}
|
|
177
|
+
if (failed) {
|
|
178
|
+
// Roll everything back, including the plugin itself.
|
|
179
|
+
for (auto it = removed.rbegin(); it != removed.rend(); ++it) fsu::rename(it->second, fsu::join(install, it->first));
|
|
180
|
+
fsu::rename(target, fsu::join(work, plugin + ".rollback"));
|
|
181
|
+
if (!parked.empty()) fsu::rename(parked, target);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (failed) {
|
|
185
|
+
fsu::remove_all(work);
|
|
186
|
+
fsu::remove_all(old);
|
|
187
|
+
say("The update could not be installed; the previous version was restored.");
|
|
188
|
+
return ApplyResult::Failed;
|
|
189
|
+
}
|
|
190
|
+
fsu::remove_all(work);
|
|
191
|
+
fsu::remove_all(old); // Windows: a renamed loaded .aex stays until the next launch's cleanup
|
|
192
|
+
fsu::remove_all(paths.staged);
|
|
193
|
+
fsu::remove_file(paths.staged_meta);
|
|
194
|
+
if (error) error->clear();
|
|
195
|
+
if (env.log) env.log(1, "Update " + staged_version + " installed; it loads the next time After Effects starts.");
|
|
196
|
+
return ApplyResult::Ok;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
} // namespace apn
|