docs(c++): LocalFileOrigin implementation guide
Covers ownership-by-exclusion algorithm, document identity, open/save workflows, capability flags, connection state, OriginManager integration, and unregister protection. Closes #131
This commit is contained in:
@@ -50,6 +50,7 @@
|
||||
# C++ API Reference
|
||||
|
||||
- [FileOrigin Interface](./reference/cpp-file-origin.md)
|
||||
- [LocalFileOrigin](./reference/cpp-local-file-origin.md)
|
||||
- [OriginManager](./reference/cpp-origin-manager.md)
|
||||
- [CommandOrigin](./reference/cpp-command-origin.md)
|
||||
- [FileOriginPython Bridge](./reference/cpp-file-origin-python.md)
|
||||
|
||||
218
docs/src/reference/cpp-local-file-origin.md
Normal file
218
docs/src/reference/cpp-local-file-origin.md
Normal file
@@ -0,0 +1,218 @@
|
||||
# LocalFileOrigin
|
||||
|
||||
`LocalFileOrigin` is the built-in, default `FileOrigin` implementation. It handles documents stored on the local filesystem without external tracking or synchronisation.
|
||||
|
||||
- **Header:** `src/Gui/FileOrigin.h`
|
||||
- **Source:** `src/Gui/FileOrigin.cpp`
|
||||
- **Origin ID:** `"local"`
|
||||
- **Display name:** `"Local Files"` (nickname `"Local"`)
|
||||
- **Type:** `OriginType::Local`
|
||||
|
||||
## Design principles
|
||||
|
||||
1. **Identity is the file path.** A document's identity under LocalFileOrigin is its `FileName` property — the absolute path on disk.
|
||||
2. **Ownership by exclusion.** LocalFileOrigin claims every document that is *not* claimed by a PLM origin. It detects this by scanning for the `SiloItemId` property; if no object in the document has it, the document is local.
|
||||
3. **Always available.** LocalFileOrigin is created by `OriginManager` at startup and cannot be unregistered. It is the mandatory fallback origin.
|
||||
4. **Thin delegation.** Most operations delegate directly to `App::GetApplication()` or `App::Document` methods. LocalFileOrigin adds no persistence layer of its own.
|
||||
|
||||
## Identity and capabilities
|
||||
|
||||
```cpp
|
||||
std::string id() const override; // "local"
|
||||
std::string name() const override; // "Local Files"
|
||||
std::string nickname() const override; // "Local"
|
||||
QIcon icon() const override; // theme icon "document-new"
|
||||
OriginType type() const override; // OriginType::Local
|
||||
```
|
||||
|
||||
### Capability flags
|
||||
|
||||
All capability queries return `false`:
|
||||
|
||||
| Method | Returns | Reason |
|
||||
|--------|---------|--------|
|
||||
| `tracksExternally()` | `false` | No external database or server |
|
||||
| `requiresAuthentication()` | `false` | No login required |
|
||||
| `supportsRevisions()` | `false` | No revision history |
|
||||
| `supportsBOM()` | `false` | No Bill of Materials |
|
||||
| `supportsPartNumbers()` | `false` | No part number system |
|
||||
| `supportsAssemblies()` | `false` | No native assembly tracking |
|
||||
|
||||
These flags control which toolbar buttons and menu items are enabled. With all flags false, the origin toolbar commands (Commit, Pull, Push, Info, BOM) are disabled for local documents.
|
||||
|
||||
### Connection state
|
||||
|
||||
LocalFileOrigin uses the base-class defaults — it is always `ConnectionState::Connected`:
|
||||
|
||||
| Method | Behaviour |
|
||||
|--------|-----------|
|
||||
| `connectionState()` | Returns `ConnectionState::Connected` |
|
||||
| `connect()` | Returns `true` immediately |
|
||||
| `disconnect()` | No-op |
|
||||
| `signalConnectionStateChanged` | Never emitted |
|
||||
|
||||
## Ownership detection
|
||||
|
||||
```cpp
|
||||
bool ownsDocument(App::Document* doc) const override;
|
||||
```
|
||||
|
||||
**Algorithm:**
|
||||
|
||||
1. Return `false` if `doc` is null.
|
||||
2. Iterate every object in the document (`doc->getObjects()`).
|
||||
3. For each object, check `obj->getPropertyByName("SiloItemId")`.
|
||||
4. If **any** object has the property, return `false` — the document belongs to a PLM origin.
|
||||
5. If **no** object has it, return `true`.
|
||||
|
||||
The constant `SiloItemId` is defined as a `static const char*` in `FileOrigin.cpp`.
|
||||
|
||||
**Edge cases:**
|
||||
|
||||
- An empty document (no objects) is owned by LocalFileOrigin.
|
||||
- A document where only some objects have `SiloItemId` is still considered PLM-owned.
|
||||
- Performance is O(n) where n is the number of objects in the document.
|
||||
|
||||
### Ownership resolution order
|
||||
|
||||
`OriginManager::findOwningOrigin()` checks non-local origins first, then falls back to LocalFileOrigin. This ensures PLM origins with specific ownership criteria take priority:
|
||||
|
||||
```
|
||||
for each registered origin (excluding "local"):
|
||||
if origin.ownsDocument(doc) → return that origin
|
||||
|
||||
if localOrigin.ownsDocument(doc) → return localOrigin
|
||||
|
||||
return nullptr
|
||||
```
|
||||
|
||||
Results are cached in `OriginManager::_documentOrigins` for O(1) subsequent lookups.
|
||||
|
||||
## Document identity
|
||||
|
||||
```cpp
|
||||
std::string documentIdentity(App::Document* doc) const override;
|
||||
std::string documentDisplayId(App::Document* doc) const override;
|
||||
```
|
||||
|
||||
Both return the same value: the document's `FileName` property (its absolute filesystem path). Returns an empty string if the document is null, not owned, or has never been saved.
|
||||
|
||||
For PLM origins these would differ — `documentIdentity` might return a UUID while `documentDisplayId` returns a part number. For local files, the path serves both purposes.
|
||||
|
||||
## Document operations
|
||||
|
||||
### Creating documents
|
||||
|
||||
```cpp
|
||||
App::Document* newDocument(const std::string& name = "") override;
|
||||
```
|
||||
|
||||
Delegates to `App::GetApplication().newDocument()`. If `name` is empty, defaults to `"Unnamed"`. The returned document has no `FileName` set until saved.
|
||||
|
||||
### Opening documents
|
||||
|
||||
```cpp
|
||||
App::Document* openDocument(const std::string& identity) override;
|
||||
App::Document* openDocumentInteractive() override;
|
||||
```
|
||||
|
||||
**`openDocument`** — Non-interactive. Takes a file path, delegates to `App::GetApplication().openDocument()`. Returns `nullptr` if the path is empty or the file cannot be loaded.
|
||||
|
||||
**`openDocumentInteractive`** — Shows a file dialog with the following behaviour:
|
||||
|
||||
1. Builds a format filter list from FreeCAD's registered import types, with `.FCStd` first.
|
||||
2. Shows `FileDialog::getOpenFileNames()` (multi-file selection).
|
||||
3. For each selected file, uses `SelectModule::importHandler()` to determine the loader module.
|
||||
4. Calls `Application::Instance->open()` with the `UserInitiatedOpenDocument` flag set.
|
||||
5. Runs `checkPartialRestore()` and `checkRestoreError()` for validation.
|
||||
6. Returns the last loaded document, or `nullptr` if the dialog was cancelled.
|
||||
|
||||
### Saving documents
|
||||
|
||||
```cpp
|
||||
bool saveDocument(App::Document* doc) override;
|
||||
bool saveDocumentAs(App::Document* doc, const std::string& newIdentity) override;
|
||||
bool saveDocumentAsInteractive(App::Document* doc) override;
|
||||
```
|
||||
|
||||
**`saveDocument`** — Requires that the document already has a `FileName` set. If the path is empty (never-saved document), returns `false`. Otherwise calls `doc->save()`.
|
||||
|
||||
**`saveDocumentAs`** — Non-interactive. Calls `doc->saveAs(newIdentity)` with the given path. Updates the document's `FileName` property as a side effect.
|
||||
|
||||
**`saveDocumentAsInteractive`** — Gets the `Gui::Document` wrapper and delegates to `guiDoc->saveAs()`, which shows a save dialog. Returns `false` if the GUI document cannot be found or the user cancels.
|
||||
|
||||
**Save workflow pattern:**
|
||||
|
||||
```
|
||||
First save: saveDocument() → false (no path)
|
||||
→ command layer calls saveDocumentAsInteractive()
|
||||
→ user picks path → saveAs()
|
||||
|
||||
Later saves: saveDocument() → true (path exists)
|
||||
→ doc->save()
|
||||
```
|
||||
|
||||
### PLM operations (not implemented)
|
||||
|
||||
These inherited base-class methods are no-ops for LocalFileOrigin:
|
||||
|
||||
| Method | Returns | Behaviour |
|
||||
|--------|---------|-----------|
|
||||
| `commitDocument(doc)` | `false` | No external commits |
|
||||
| `pullDocument(doc)` | `false` | No remote to pull from |
|
||||
| `pushDocument(doc)` | `false` | No remote to push to |
|
||||
| `showInfo(doc)` | void | No-op |
|
||||
| `showBOM(doc)` | void | No-op |
|
||||
|
||||
## OriginManager integration
|
||||
|
||||
### Lifecycle
|
||||
|
||||
LocalFileOrigin is created in the `OriginManager` constructor via `ensureLocalOrigin()`:
|
||||
|
||||
```cpp
|
||||
auto localOrigin = std::make_unique<LocalFileOrigin>();
|
||||
_origins[LOCAL_ORIGIN_ID] = std::move(localOrigin);
|
||||
_currentOriginId = LOCAL_ORIGIN_ID;
|
||||
```
|
||||
|
||||
It is set as the default current origin. It is the only origin that exists before any Python workbenches load.
|
||||
|
||||
### Unregister protection
|
||||
|
||||
`OriginManager::unregisterOrigin()` explicitly blocks removal of the local origin:
|
||||
|
||||
```cpp
|
||||
if (id == LOCAL_ORIGIN_ID) {
|
||||
Base::Console().warning(
|
||||
"OriginManager: Cannot unregister built-in local origin\n");
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
### Preference persistence
|
||||
|
||||
The current origin ID is saved to `User parameter:BaseApp/Preferences/General/Origin` under the key `CurrentOriginId`. On restart, if the saved ID is not `"local"` but the corresponding origin hasn't been registered yet, LocalFileOrigin remains the active origin until a Python workbench registers the saved origin.
|
||||
|
||||
## Constants
|
||||
|
||||
Defined in `FileOrigin.cpp`:
|
||||
|
||||
```cpp
|
||||
static const char* SILO_ITEM_ID_PROP = "SiloItemId";
|
||||
```
|
||||
|
||||
Defined in `OriginManager.cpp`:
|
||||
|
||||
```cpp
|
||||
static const char* LOCAL_ORIGIN_ID = "local";
|
||||
static const char* PREF_PATH = "User parameter:BaseApp/Preferences/General/Origin";
|
||||
static const char* PREF_CURRENT_ORIGIN = "CurrentOriginId";
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [FileOrigin Interface](./cpp-file-origin.md) — abstract base class
|
||||
- [OriginManager](./cpp-origin-manager.md) — singleton registry and document resolution
|
||||
- [FileOriginPython Bridge](./cpp-file-origin-python.md) — Python adapter for custom origins
|
||||
- [SiloOrigin](./python-silo-origin.md) — PLM origin that contrasts with LocalFileOrigin
|
||||
Reference in New Issue
Block a user