Bug: FileManager processFrontmatter failing to save data when used in a loop
qaptoR opened this issue · 3 comments
Steps to reproduce:
This has happened in multiple contexts for getting an array of TFiles
eg: using TFolder.children
or like with the code example below, using the Dataview API to return an array from a specific query.
At first I thought the problem was that the TFiles in the original array that is returned were being swapped around, so that when looping over with (file of files)
syntax there were misses and the solution of creating a second array to store the files after checking for validity seemed to work when using TFolder.children
.
However, as you can see, though I'm using that fix in the snippet below, it turns out that it doesn't work.
The files definitely are getting processed (when you open the file the actual text is changed), but the frontmatter in the metadatacache is not updated with the new value.
Therefore, it's clearly not my initial suspicion that the array was the issue, but that the TFile is not being saved properly.
If I open the file in the editor make any sort of change to the text and then save manually, suddenly the metadatacache matches the text.
A simple reload of Obsidian does not refresh the metadatacache from this issue.
When I look in app.workspace.activeEditor from the console, there is a discrepency between the lastFrontmatter
and the lasSavedData
What's interesting is that this problem only occurs on some of the array, something like 1/5th of the size, but possibly it becomes an issue once the array reaches 10-14 entries, it seems to happen very inconsistently depending on the list of files, sometimes affecting the same files (on repeated attempts of the same list) and sometimes only a few (but usually of the same set of affected files). yet there doesn't seem to be a clear pattern of what it is about the files that is making them affected in this way.
Callback for Command
export const updatePathsSeclectionQuery = async function (editor :Editor, view :MarkdownView) {
const query :string = editor.getSelection()
try {
const updateArgs :GenericTwoInputArgs = await GenericTwoInputTextModal.Request(
view.app, {
fieldOne: "ARCH_IVE Path to Change",
fieldTwo: "New ARCH_IVE Path",
})
const dv = getAPI()
if (!dv) throw new Error("Could not get Dataview API");
const results = await dv.tryQuery(query)
const files = []
for (const entry of results.values) {
if (!('path' in entry)) continue
const file = view.app.vault.getAbstractFileByPath(entry.path)
if (!(file instanceof TFile) || file.extension != 'md') continue
files.push(file)
}
for (const file of files) {
await updateFile.bind(view)(file, updateArgs)
}
} catch (e) {
console.log(e)
}
}
function to process frontmatter
export const updateFile = async function (file :TFile, args :GenericTwoInputArgs) {
if (!this.app.metadataCache.getFileCache(file)?.frontmatter?.['arch-ive-path']) {
console.log("no path found")
return
}
const newArchivePath = this.app.metadataCache.getFileCache(file)
?.frontmatter?.['arch-ive-path'].replace(args.fieldOne, args.fieldTwo)
try {
await this.app.fileManager.processFrontMatter(file, (fm :any) => {
fm['arch-ive-path'] = newArchivePath
})
await tryHashNoCollision(file)
} catch (e) {
console.log(e)
}
}
You're doing weird stuff to the internal API like file.saving = true
(Don't do that! saving
is there for a reason!). Plus lastFrontmatter
and lastSavedData
are also both internal data structures that you should not be using or relying on.
If you can find a case that is reproducible and purely using the public API, I can look into it, but otherwise there isn't much I can do.
a) sorry, I'm not actually relying on file.saving
. That was a misplaced holdover from my just firing shots into the dark to try and figure out what could be the issue.
b) I'm also not relying on lastFrontmatter
or lastSavedData
. I only mentioned them because from what I could see those values were not the same and I expect that they ought to be, no? They were just a starting point for looking for a
c) as for a reproducible case, as I mentioned before, the issue exists whether I use a Dataview returned array of TFiles or just access TFolder.children
.
The function above called updateFile
is the culprit, so a minimum reproducible case is
for (const file of someTFolder.children) {
await updateFile.bind(view)(file, {fieldOne: "some-field", fieldTwo: "some-new-field"})
}
I also forgot that updateFile
calls tryHashNoCollision
, so here is that as well
export async function tryHashNoCollision (file :TFile) {
const frontmatter = this.app.metadataCache.getFileCache(file)?.frontmatter
if (!frontmatter?.['arch-ive-path'] || !frontmatter?.['arch-ive-title']) return
const toHash = frontmatter['arch-ive-path'] + frontmatter['arch-ive-title']
let i= 0;
do {
const basenameHash = getBasenameHash(toHash + i.toString(), 10)
const newPath = `${file.parent.path}/${basenameHash}.md`
const lookup = this.app.vault.getAbstractFileByPath(newPath)
if (lookup && lookup instanceof TFile) {
i += 1;
continue;
}
await this.app.fileManager.renameFile(file, newPath)
break;
} while (true)
}
So you rename the file immediately after editing it? Is that what's causing your issue?
Can you come up with a minimally reproducible code sample that we can use to test?