forked from AnimaBeyondDevelop/AnimaBeyondFoundry
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmigrate.js
More file actions
249 lines (225 loc) · 7.88 KB
/
Copy pathmigrate.js
File metadata and controls
249 lines (225 loc) · 7.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
import { Logger } from '../../utils';
import { ABFSettingsKeys } from '../../utils/registerSettings';
import { ABFActor } from '../actor/ABFActor';
import { ABFDialogs } from '../dialogs/ABFDialogs';
import ABFItem from '../items/ABFItem';
import * as MigrationList from './migrations';
/** @typedef {import('./migrations/Migration').Migration} Migration */
/**
* @param {Migration} migration
* @returns {boolean} Wether the migration applies or not.
*/
function migrationApplies(migration) {
/** @type {number} */
const currentVersion = game.settings.get(
'animabf',
ABFSettingsKeys.SYSTEM_MIGRATION_VERSION
);
if (currentVersion < migration.version) {
return true;
}
if (game.settings.get('animabf', ABFSettingsKeys.DEVELOP_MODE)) {
Logger.warn(
`Migration ${migration.version} needs not to be applied, current system migration version is ${currentVersion}.`
);
}
return false;
}
/**
* Migrates a collection of items. If the collection are items belonging to an actor or pack,
* a context needs to be provided for the `updateDocuments(...)` function.
* @param {ABFItem[]} items
* @param {Migration} migration
* @param {{parent: ABFActor} | {pack: string} | {}} context - context for the Item.updateDocuments call
*/
async function migrateItemCollection(items, migration, context = {}) {
if (migration.filterItems) items = items.filter(migration.filterItems);
const length = items.length ?? items.size; // takes care of the case of a DocumentCollection
if (length === 0 || !migration.updateItem) return;
Logger.log(`Migrating ${length} Items.`);
const migrated = await Promise.all(items.map(i => migration.updateItem(i)));
const updates = migrated
.map(i => {
if (!i) return;
const { _id, name, system } = i;
return { _id, name, system };
})
.filter(u => u);
await ABFItem.updateDocuments(updates, context);
}
/**
* Migrates a collection of actors, applying the migration to their owned items also.
* @param {ABFActor[]} actors
* @param {Migration} migration
* @param {{parent: ABFActor} | {pack: string} | {}} context - context for the updateDocuments calls
*/
async function migrateActorCollection(actors, migration, context = {}) {
if (migration.filterActors) actors = actors.filter(migration.filterActors);
const length = actors.length ?? actors.size; // takes care of the case of a DocumentCollection
if (length === 0 || (!migration.updateItem && !migration.updateActor)) return;
Logger.log(`Migrating ${length} Actors.`);
if (migration.updateItem) {
await Promise.all(
actors.map(async a => migrateItemCollection(a.items, migration, { parent: a }))
);
}
if (migration.updateActor) {
const migrated = await Promise.all(actors.map(a => migration.updateActor(a)));
const updates = migrated
.map(a => {
if (!a) return;
const { _id, name, system } = a;
return { _id, name, system };
})
.filter(u => !!u);
await ABFActor.updateDocuments(updates, context);
}
}
/**
* Migrates the actors from unlinked tokens presents on a collection of scenes
* @param {Scene[]} scenes
* @param {Migration} migration
* @param {{parent: ABFActor} | {pack: string} | {}} context - context for the updateDocuments calls
*/
async function migrateUnlinkedActors(scenes, migration, context) {
const length = items.length || items.size; // takes care of the case of a DocumentCollection
if (length === 0 || (!migration.updateItem && !migration.updateActor)) return;
// This has to be performed individually instead of using directly migrateItemCollection() and
// migrateActorCollection() since unlinked (syntetic) actors won't get updated on batch
// Actors.updateDocuments() updates. {parent: TOKEN} has to be specified for the update to be
// completed.
for (const scene of scenes) {
for (const token of scene.tokens.filter(token => !token.actorLink && token.actor)) {
await migrateActorCollection([token.actor], migration, { parent: token });
}
}
}
/**
* Migrates world items
* @param {Migration} migration
*/
async function migrateWorldItems(migration) {
if (!migration.updateItem) return;
await migrateItemCollection(game.items, migration);
}
/**
* Migrates world actors (including unlinked tokens on world's scenes), and their owned items.
* @param {Migration} migration
*/
async function migrateWorldActors(migration) {
if (!migration.updateActor && !migration.updateItem) return;
migrateActorCollection(game.actors, migration);
migrateUnlinkedActors(game.scenes, migration);
}
/**
* @param {Migration} migration
*/
async function migratePacks(migration) {
// load packs with actors, items or scenes
const packTypes = migration.updateItem
? ['Actor', 'Item', 'Scene']
: ['Actor', 'Scene'];
let packs = await Promise.all(
game.packs
.filter(pack => packTypes.includes(pack.metadata.type))
.map(async pack => {
const packObj = {
pack: pack,
wasLocked: pack.locked,
id: pack.metadata.id,
type: pack.metadata.type
};
await pack.configure({ locked: false });
packObj.documents = await pack.getDocuments();
return packObj;
})
);
const migrate = {
Actor: migrateActorCollection,
Item: migrateItemCollection,
Scene: migrateUnlinkedActors
};
await Promise.all(
packs.map(pack => migrate[pack.type](pack.documents, migration, { pack: pack.id }))
);
// Lock again packs which where locked
await Promise.all(
packs
.filter(packObject => packObject.wasLocked)
.map(async packObject => {
await packObject.pack.configure({ locked: true });
})
);
}
/**
* @param {Migration} migration
* @todo Implement this function
*/
function migrateTokens(migration) {
if (migration.updateToken) {
throw new Error(
'AnimaBF | Trying to update tokens with a migration, but `migrateTokens()` function in `migrate.js` not defined yet'
);
}
}
/**
* @param {Migration} migration - The migration version to be applied
* @returns {Promise<boolean>} - Whether the migration has been succesfully applied or not.
*/
async function applyMigration(migration) {
try {
Logger.log(`Applying migration ${migration.version}.`);
await migrateWorldItems(migration);
await migrateWorldActors(migration);
await migratePacks(migration);
migrateTokens(migration);
migration.migrate?.();
Logger.log(`Migration ${migration.version} completed.`);
game.settings.set(
'animabf',
ABFSettingsKeys.SYSTEM_MIGRATION_VERSION,
migration.version
);
// TODO: add french translation for the warning dialog also.
await ABFDialogs.prompt(
game.i18n.format('dialogs.migrations.success', {
version: migration.version,
title: migration.title
})
);
return true;
} catch (err) {
Logger.error(`Error when trying to apply migration ${migration.version}:\n${err}`);
await ABFDialogs.prompt(
game.i18n.format('dialogs.migrations.error', {
version: migration.version,
error: err
})
);
}
return false;
}
/** This is the only function on the script meant to be called from outside the script */
export async function applyMigrations() {
if (!game.user.isGM) {
return;
}
const migrations = Object.values(MigrationList).filter(migration =>
migrationApplies(migration)
);
migrations.sort((a, b) => a.version - b.version);
for (const migration of migrations) {
const result = await ABFDialogs.confirm(
game.i18n.localize('dialogs.migrations.title'),
`${game.i18n.localize('dialogs.migrations.content')}<br><hr><br>` +
'<h4>Details of the migration (only English available):</h4>' +
`<strong>Title:</strong> ${migration.title}<br>` +
`<strong>Description:</strong> ${migration.description}`
);
if (result === 'confirm') {
await applyMigration(migration);
} else {
break;
}
}
}