Skip to content

Commit

Permalink
Merge pull request #10397 from DestinyItemManager/outdated-manifest
Browse files Browse the repository at this point in the history
React to outdated manifest
  • Loading branch information
bhollis authored May 10, 2024
2 parents cf3c363 + 6c4b938 commit 472042d
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 99 deletions.
4 changes: 1 addition & 3 deletions config/i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -895,9 +895,7 @@
"Manifest": {
"Download": "Downloading latest Destiny info database from Bungie...",
"Error": "Error loading Destiny info database:\n{{error}}\nReload to retry.",
"Load": "Loading Destiny info database...",
"Outdated": "Outdated Destiny info database",
"OutdatedExplanation": "Bungie has updated their Destiny info database. Reload DIM to pick up the new info. Note that some things in DIM may not work for a few hours after Bungie updates Destiny, as the new data propagates through their systems."
"Load": "Loading Destiny info database..."
},
"Milestone": {
"Daily": "Daily Challenge",
Expand Down
150 changes: 86 additions & 64 deletions src/app/inventory/d2-stores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { t } from 'app/i18next-t';
import { processInGameLoadouts } from 'app/loadout-drawer/loadout-type-converters';
import { inGameLoadoutLoaded } from 'app/loadout/ingame/actions';
import { loadCoreSettings } from 'app/manifest/actions';
import { checkForNewManifest } from 'app/manifest/manifest-service-json';
import { d2ManifestSelector, manifestSelector } from 'app/manifest/selectors';
import { loadingTracker } from 'app/shell/loading-tracker';
import { get, set } from 'app/storage/idb-keyval';
Expand Down Expand Up @@ -242,6 +243,8 @@ function loadProfile(
};
}

let lastCheckedManifest = 0;

function loadStoresData(
account: DestinyAccount,
firstTime: boolean,
Expand All @@ -260,94 +263,113 @@ function loadStoresData(
resetItemIndexGenerator();

try {
const [defs, profileInfo] = await Promise.all([
const [originalDefs, profileInfo] = await Promise.all([
dispatch(getDefinitions()),
dispatch(loadProfile(account, firstTime)),
]);

let defs = originalDefs;

// If we switched account since starting this, give up
if (account !== currentAccountSelector(getState())) {
return;
}

if (!defs || !profileInfo) {
return;
}
for (let i = 0; i < 2; i++) {
if (!defs || !profileInfo) {
return;
}

const { profile: profileResponse, live, readOnly } = profileInfo;
const { profile: profileResponse, live, readOnly } = profileInfo;

const stopTimer = timer('Process inventory');
const stopTimer = timer('Process inventory');

const buckets = d2BucketsSelector(getState())!;
const customStats = customStatsSelector(getState());
const stores = buildStores(
{
defs,
buckets,
customStats,
profileResponse,
},
transaction,
);
const buckets = d2BucketsSelector(getState())!;
const customStats = customStatsSelector(getState());
const stores = buildStores(
{
defs,
buckets,
customStats,
profileResponse,
},
transaction,
);

// One reason stores could have errors is if the manifest was not up
// to date. Check to see if it has updated, and if so, download it and
// immediately try again.
if (stores.some((s) => s.hadErrors)) {
if (lastCheckedManifest - Date.now() < 5 * 60 * 1000) {
return;
}
lastCheckedManifest = Date.now();

if (readOnly) {
for (const store of stores) {
store.hadErrors = true;
for (const item of store.items) {
item.lockable = false;
item.trackable = false;
item.notransfer = true;
item.taggable = false;
if (await checkForNewManifest()) {
defs = await dispatch(getDefinitions(true));
continue; // go back to the top of the loop with the new defs
}
}

if (readOnly) {
for (const store of stores) {
store.hadErrors = true;
for (const item of store.items) {
item.lockable = false;
item.trackable = false;
item.notransfer = true;
item.taggable = false;
}
}
}
}

const currencies = processCurrencies(profileResponse, defs);
const currencies = processCurrencies(profileResponse, defs);

const loadouts = processInGameLoadouts(profileResponse, defs);
const loadouts = processInGameLoadouts(profileResponse, defs);

stopTimer();
stopTimer();

const stateSpan = transaction?.startChild({
op: 'updateInventoryState',
});
const stopStateTimer = timer('Inventory state update');
const stateSpan = transaction?.startChild({
op: 'updateInventoryState',
});
const stopStateTimer = timer('Inventory state update');

// If we switched account since starting this, give up before saving
if (account !== currentAccountSelector(getState())) {
return;
}
// If we switched account since starting this, give up before saving
if (account !== currentAccountSelector(getState())) {
return;
}

if (!getCurrentStore(stores)) {
errorLog('d2-stores', 'No characters in profile');
dispatch(
error(
new DimError(
'Accounts.NoCharactersTitle',
t('Accounts.NoCharacters'),
).withNoSocials(),
),
);
return;
}
if (!getCurrentStore(stores)) {
errorLog('d2-stores', 'No characters in profile');
dispatch(
error(
new DimError(
'Accounts.NoCharactersTitle',
t('Accounts.NoCharacters'),
).withNoSocials(),
),
);
return;
}

// Cached loads can come from IDB, which can be VERY outdated, so don't
// remove item tags/notes based on that. We also refuse to clean tags if
// the profile is too old in wall-clock time. Technically we could do
// this *only* based on the minted timestamp, but there's no real point
// in cleaning items for cached loads since they presumably were cleaned
// already.
const profileMintedDate = new Date(profileResponse.responseMintedTimestamp ?? 0);
if (live && Date.now() - profileMintedDate.getTime() < FRESH_ENOUGH_TO_CLEAN_INFOS) {
dispatch(cleanInfos(stores));
}
dispatch(update({ stores, currencies }));
dispatch(inGameLoadoutLoaded(loadouts));
// Cached loads can come from IDB, which can be VERY outdated, so don't
// remove item tags/notes based on that. We also refuse to clean tags if
// the profile is too old in wall-clock time. Technically we could do
// this *only* based on the minted timestamp, but there's no real point
// in cleaning items for cached loads since they presumably were cleaned
// already.
const profileMintedDate = new Date(profileResponse.responseMintedTimestamp ?? 0);
if (live && Date.now() - profileMintedDate.getTime() < FRESH_ENOUGH_TO_CLEAN_INFOS) {
dispatch(cleanInfos(stores));
}
dispatch(update({ stores, currencies }));
dispatch(inGameLoadoutLoaded(loadouts));

stopStateTimer();
stateSpan?.finish();
stopStateTimer();
stateSpan?.finish();

return stores;
return stores;
}
} catch (e) {
errorLog('d2-stores', 'Error loading stores', e);
reportException('d2stores', e);
Expand Down
4 changes: 1 addition & 3 deletions src/app/inventory/store/d2-item-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ import { Draft } from 'immer';
import _ from 'lodash';
import memoizeOne from 'memoize-one';
import { D2ManifestDefinitions } from '../../destiny2/d2-definitions';
import { warnMissingDefinition } from '../../manifest/manifest-service-json';
import { reportException } from '../../utils/sentry';
import { InventoryBuckets } from '../inventory-buckets';
import { DimItem, DimPursuitExpiration, DimQuestLine } from '../item-types';
Expand Down Expand Up @@ -253,9 +252,8 @@ export function makeItem(
? itemComponents?.instances.data?.[item.itemInstanceId] ?? emptyObject()
: emptyObject();

// Missing definition?
// Missing definition
if (!itemDef) {
warnMissingDefinition();
return undefined;
}

Expand Down
31 changes: 5 additions & 26 deletions src/app/manifest/manifest-service-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,32 +73,11 @@ const localStorageKey = 'd2-manifest-version';
const idbKey = 'd2-manifest';
let version: string | null = null;

/**
* This tells users to reload the app. It fires no more
* often than every 10 seconds, and only warns if the manifest
* version has actually changed.
*/
export const warnMissingDefinition = _.debounce(
async () => {
if ($DIM_FLAVOR !== 'test') {
const data = await d2GetManifest();
// If none of the paths (for any language) matches what we downloaded...
if (version && !Object.values(data.jsonWorldContentPaths).includes(version)) {
// The manifest has updated!
showNotification({
type: 'warning',
title: t('Manifest.Outdated'),
body: t('Manifest.OutdatedExplanation'),
});
}
}
},
10000,
{
leading: true,
trailing: false,
},
);
export async function checkForNewManifest() {
const data = await d2GetManifest();
// If none of the paths (for any language) matches what we downloaded...
return version && !Object.values(data.jsonWorldContentPaths).includes(version);
}

const getManifestAction = _.once(
(tableAllowList: string[]): ThunkResult<AllDestinyManifestComponents> =>
Expand Down
4 changes: 1 addition & 3 deletions src/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -880,9 +880,7 @@
"Manifest": {
"Download": "Downloading latest Destiny info database from Bungie...",
"Error": "Error loading Destiny info database:\n{{error}}\nReload to retry.",
"Load": "Loading Destiny info database...",
"Outdated": "Outdated Destiny info database",
"OutdatedExplanation": "Bungie has updated their Destiny info database. Reload DIM to pick up the new info. Note that some things in DIM may not work for a few hours after Bungie updates Destiny, as the new data propagates through their systems."
"Load": "Loading Destiny info database..."
},
"Milestone": {
"Daily": "Daily Challenge",
Expand Down

0 comments on commit 472042d

Please sign in to comment.