Skip to content

Commit 468aab2

Browse files
committed
fixup! feat: improve ZipFolderPlugin behaviour for different cases
Signed-off-by: Salvatore Martire <4652631+salmart-dev@users.noreply.github.com>
1 parent df9c1be commit 468aab2

2 files changed

Lines changed: 264 additions & 1 deletion

File tree

apps/dav/lib/Connector/Sabre/ZipFolderPlugin.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ private function logStreamErrors(mixed $stream, string $path, float|int $expecte
136136
}
137137

138138
if (!($streamMetadata['eof'] ?? true) || $readFileSize != $expectedFileSize) {
139-
return $this->l10n->t('Read %d out of %d bytes from storage. This means the connection may have been closed due to a network/storage error.', [$expectedFileSize, $readFileSize]);
139+
return $this->l10n->t('Read %d out of %d bytes from storage. This means the connection may have been closed due to a network/storage error.', [$readFileSize, $expectedFileSize]);
140140
}
141141

142142
return null;

apps/workflowengine/README.md

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
# Workflow engine developer guide
2+
3+
This app provides the infrastructure behind Nextcloud's **flows** UI.
4+
5+
The goal of this document is to give a developer who does not know the app a quick mental model of how the pieces fit together, and how to add new ones.
6+
7+
## Terminology
8+
9+
A few names are used for closely related things:
10+
11+
- **Flow**: the user-facing term used in the settings UI.
12+
- **Workflow rule**: the stored configuration record in the backend/API.
13+
- **Operation**: the action part of a flow, implemented by an app through `OCP\WorkflowEngine\IOperation`.
14+
15+
In practice, a **flow / workflow rule** is:
16+
17+
1. an **operation** to run,
18+
2. for a given **entity**,
19+
3. on one or more **events** exposed by that entity,
20+
4. if all configured **checks** match,
21+
5. within a given **scope**.
22+
23+
So when the UI says “flow”, think “a configured rule made of operation + entity/events + checks”.
24+
25+
## The core concepts
26+
27+
### Entity
28+
29+
An entity is the thing the rule is about.
30+
31+
Backend contract: `OCP\WorkflowEngine\IEntity`
32+
33+
An entity defines:
34+
35+
- a display name and icon for the UI,
36+
- the events it supports,
37+
- how runtime context is prepared for rule matching,
38+
- whether a user is allowed to run user-scoped flows against the current context.
39+
40+
The built-in entity in this app is `OCA\WorkflowEngine\Entity\File`.
41+
It exposes file-related events such as create, update, rename, delete, access, copy, and tag assignment.
42+
43+
A good way to think about an entity is: **it provides the event source and the subject that checks evaluate against**.
44+
45+
### Event
46+
47+
Events are declared by an entity via `IEntity::getEvents()`.
48+
Each event has:
49+
50+
- a machine name (`IEntityEvent::getEventName()`),
51+
- a display name (`IEntityEvent::getDisplayName()`).
52+
53+
A configured rule stores both the chosen entity class and the selected event names.
54+
During validation, `Manager::validateEvents()` ensures the selected events really belong to the chosen entity.
55+
56+
### Check
57+
58+
A check is a condition that must evaluate to true for a rule to match.
59+
60+
Backend contract: `OCP\WorkflowEngine\ICheck`
61+
62+
A check defines:
63+
64+
- how to execute the condition (`executeCheck()`),
65+
- how to validate configuration (`validateCheck()`),
66+
- which entities it supports (`supportedEntities()`),
67+
- which scopes it is available in (`isAvailableForScope()`).
68+
69+
Important details:
70+
71+
- A rule must contain at least one check.
72+
- All checks of a rule must pass for the rule to match.
73+
- `Manager::validateOperation()` rejects checks that do not implement `ICheck`, are not registered/resolvable, or are not allowed for the selected entity.
74+
75+
There are two useful specializations:
76+
77+
- `IEntityCheck`: the engine passes the entity subject to the check.
78+
- `IFileCheck`: the engine additionally passes file storage/path information.
79+
80+
This app ships several built-in checks in `lib/Check/`, for example:
81+
82+
- file name,
83+
- file MIME type,
84+
- file size,
85+
- file system tags,
86+
- request URL,
87+
- remote address,
88+
- user agent,
89+
- request time,
90+
- user group membership.
91+
92+
### Operation
93+
94+
An operation is the action part of the rule.
95+
96+
Backend contract: `OCP\WorkflowEngine\IOperation`
97+
98+
An operation defines:
99+
100+
- how it appears in the UI (`getDisplayName()`, `getDescription()`, `getIcon()`),
101+
- which scopes it supports (`isAvailableForScope()`),
102+
- how its stored configuration is validated (`validateOperation()`),
103+
- what happens when a matching event is received (`onEvent()`).
104+
105+
Important clarification: this app itself mainly provides the engine, entity, and checks. It does **not** ship general built-in operations today (`Manager::getBuildInOperators()` returns an empty list). In real installations, operations are usually contributed by other apps.
106+
107+
Example: `files_accesscontrol` registers `OCA\FilesAccessControl\Operation`, which blocks access to files when its configured checks match.
108+
109+
### Complex operation
110+
111+
Contract: `OCP\WorkflowEngine\IComplexOperation`
112+
113+
Use `IComplexOperation` when the operation cannot rely on the engine's normal “listen to entity events and call `onEvent()`” flow.
114+
115+
A complex operation is responsible for more of its own triggering logic. It provides `getTriggerHint()` so the UI can explain to the user when it becomes active.
116+
117+
Note: for complex operations, the configured event list may be empty, and that is accepted by `Manager::validateEvents()`.
118+
119+
### Specific operation
120+
121+
Contract: `OCP\WorkflowEngine\ISpecificOperation`
122+
123+
Use `ISpecificOperation` if the operation only makes sense for exactly one entity type. The operation returns that fixed entity class from `getEntityId()`, and the UI can avoid offering other entities.
124+
125+
## What a flow looks like in practice
126+
127+
A stored workflow rule contains roughly these pieces:
128+
129+
- `class`: the operation class,
130+
- `entity`: the entity class,
131+
- `events`: the selected entity event names,
132+
- `checks`: the configured checks,
133+
- `operation`: operation-specific configuration payload,
134+
- `scope`: admin or user scope (stored separately in `flow_operations_scope`).
135+
136+
The admin/personal settings UI phrases this roughly as:
137+
138+
- **When** event X happens for entity Y,
139+
- **and** check A matches,
140+
- **and** check B matches,
141+
- then perform **operation** Z.
142+
143+
## How matching works at runtime
144+
145+
The runtime path is:
146+
147+
1. `Application::registerRuleListeners()` reads configured operations/events.
148+
2. For each configured event, it registers an event listener.
149+
3. When the event fires, the engine creates an `IRuleMatcher`.
150+
4. The entity prepares the matcher with runtime context via `IEntity::prepareRuleMatcher()`.
151+
5. The operation receives `onEvent()`.
152+
6. The operation asks the matcher for matching flows.
153+
7. The matcher loads rules for the relevant scopes and evaluates all checks.
154+
8. If a rule matches, the operation decides what to do with that result.
155+
156+
`RuleMatcher` is the component that actually evaluates the checks.
157+
It only returns a match when **all** checks in the configured rule pass.
158+
159+
## Scope model
160+
161+
The engine supports two scopes through `OCP\WorkflowEngine\IManager`:
162+
163+
- `SCOPE_ADMIN`: global rules configured by admins,
164+
- `SCOPE_USER`: personal rules configured by a user.
165+
166+
Things to keep in mind:
167+
168+
- operations and checks can opt into admin scope only, or support user scope too,
169+
- the settings UI filters available operations/checks by the current scope,
170+
- the matcher checks global scope first and may also expand to additional user scopes if the entity says the user is legitimized for the current context.
171+
172+
If you add a new entity/check/operation, be explicit about which scopes it supports.
173+
174+
## Concrete example
175+
176+
A typical file-based flow looks like this:
177+
178+
- **Entity**: `OCA\WorkflowEngine\Entity\File`
179+
- **Event**: “File created”
180+
- **Checks**:
181+
- `FileMimeType` is `application/pdf`
182+
- `UserGroupMembership` is `Legal`
183+
- **Operation**: an operation provided by another app, for example access control
184+
185+
At runtime:
186+
187+
1. the file entity converts the incoming file event into matcher context,
188+
2. the matcher loads all rules for the operation in the active scopes,
189+
3. `FileMimeType` and `UserGroupMembership` are executed,
190+
4. if both pass, the operation's logic applies.
191+
192+
## How to add a new entity
193+
194+
1. Implement `OCP\WorkflowEngine\IEntity`.
195+
2. Return a clear name, icon, and list of supported events.
196+
3. In `prepareRuleMatcher()`, provide the matcher with the subject/context that checks will need.
197+
4. Implement `isLegitimatedForUserId()` if user-scoped flows can apply to contexts owned or shared with other users.
198+
5. Register the entity when `RegisterEntitiesEvent` is dispatched.
199+
200+
Typical registration pattern in your app bootstrap:
201+
202+
- register an event listener for `OCP\WorkflowEngine\Events\RegisterEntitiesEvent`,
203+
- inside the listener call `$event->registerEntity($yourEntity)`.
204+
205+
## How to add a new check
206+
207+
1. Implement `OCP\WorkflowEngine\ICheck`.
208+
2. If the check needs the entity subject, also implement `IEntityCheck`.
209+
3. If it needs file storage/path data, implement `IFileCheck`.
210+
4. Validate operators and values in `validateCheck()`.
211+
5. Restrict supported entities with `supportedEntities()` when needed.
212+
6. Restrict scopes with `isAvailableForScope()` when needed.
213+
7. Register the check on `RegisterChecksEvent`.
214+
215+
A good check is small and focused: one condition, one clear meaning.
216+
217+
## How to add a new operation
218+
219+
1. Implement `OCP\WorkflowEngine\IOperation`.
220+
2. Provide display metadata for the settings UI.
221+
3. Validate your serialized operation payload in `validateOperation()`.
222+
4. Decide whether the operation is:
223+
- a normal operation (`IOperation`),
224+
- a complex operation (`IComplexOperation`),
225+
- a specific operation (`ISpecificOperation`),
226+
- or a combination such as `files_accesscontrol`, which is both complex and specific.
227+
5. Register the operation on `RegisterOperationsEvent`.
228+
229+
Use a normal `IOperation` when the engine can trigger you directly from configured entity events.
230+
Use `IComplexOperation` when your app needs to own the triggering mechanism itself.
231+
Use `ISpecificOperation` when only one entity type makes sense.
232+
233+
## Optional UI integration for custom checks and operations
234+
235+
The backend contracts are enough to participate in matching, but apps can also improve the settings UI.
236+
237+
The frontend exposes:
238+
239+
- `window.OCA.WorkflowEngine.registerCheck(...)`
240+
- `window.OCA.WorkflowEngine.registerOperator(...)`
241+
242+
Use these to provide custom editors, placeholders, validation helpers, colors, or web components for your check/operation configuration.
243+
244+
## Validation notes and caveats
245+
246+
- A rule without checks is invalid.
247+
- Non-complex operations must have at least one selected event.
248+
- A check can be valid globally but still be rejected for a specific entity if `supportedEntities()` does not include that entity class.
249+
- Scope support is validated both in the UI and in backend validation.
250+
- If a configured class cannot be resolved anymore, matching may skip it at runtime; validation is stricter when saving rules.
251+
252+
## Where to look in the code
253+
254+
Good starting points:
255+
256+
- `lib/Manager.php` — registration, validation, persistence, built-in checks/entities
257+
- `lib/Service/RuleMatcher.php` — rule evaluation
258+
- `lib/AppInfo/Application.php` — runtime event listener wiring
259+
- `lib/Entity/File.php` — reference entity implementation
260+
- `lib/Check/` — reference check implementations
261+
- `src/components/Rule.vue` — how the UI presents a configured flow
262+
263+
If you are new to the app, start by reading `File`, one small check implementation, and one external operation such as `files_accesscontrol`'s `Operation` together. That gives the full picture quickly.

0 commit comments

Comments
 (0)