Problem/Motivation
While working on #1932652: Add image uploading to WYSIWYGs through editor.module, I wanted to implement a hook_uninstall() hook to remove all file usages by editor.module upon uninstallation.
I also noticed that image.module erroneously does not yet do this. (file.module doesn't need to do this, because it owns/provides the file usage service.)
This is necessary for the file usage service to be of any use, because without this "delete all file usages by a module upon its uninstallation", it will not be updated accordingly, and hence be completely inaccurate.
The problem is two-fold:
- There is no explicit support for uninstalling a module in
FileUsageInterface: you have to callFileUsageInterface::delete()for every file that is used by the module being uninstalled. This means potentially millions of things needing to be loaded. - Performing a DB query to do the above is also impossible, because the file usage service has a swappable storage back-end…
Proposed resolution
Two options:
- Add a new
FileUsageInterface::moduleUninstalled($module)method. This would essentially do something likeDELETE * FROM file_usage WHERE module = $module. - Make the first parameter of
FileUsageInterface::delete(),$file, optional. The behavior would then be the same as the above.
Remaining tasks
TBD
User interface changes
None.
API changes
TBD.
Related Issues
#1932652: Add image uploading to WYSIWYGs through editor.module
https://drupal.org/node/1969502#comment-7295942
| Comment | File | Size | Author |
|---|---|---|---|
| #25 | module_uninstall_delete_file_usage-2060405-25.patch | 4.45 KB | wim leers |
| #11 | module_uninstall_delete_file_usage-2060405-11.patch | 4.45 KB | wim leers |
Comments
Comment #1
wim leersThis would be an API change.
Comment #2
swentel commentedWell, files can reused no ? So as long as the delete query doesn't blindly delete them, I guess we're ok, unless I'm missing something.
Comment #3
gábor hojtsyI'm not sure how the criticality of this is justified. Drupal 7 file module did not do any uninstallation of its files either: http://drupalcode.org/project/drupal.git/blob/refs/heads/7.x:/modules/fi... - although D7 had the file_usage table managed by system module. So does seem to be an existing issue in Drupal 7.
Comment #4
dave reidNot sure if the best route is some kind of FileUsageInterface::deleteByModule() or FileUsageInterface::deleteByConditions(array('module' => 'image')), but I would probably say this is major.
I would guess that file module should implement hook_modules_uninstalled() and take care of this for any module that uses file usage so they don't have to worry about it.
Comment #5
wim leers#2: the fact that you're missing is that the file usage service is supposed to register how many times something is being used correctly. By not cleaning up file usages by modules that no longer are in use, it is painting an inaccurate picture.
I think you misunderstood it somewhere along the way? :)
Comment #6
wim leersCross-post.
Comment #7
wim leers+9000
I had no idea
hook_modules_uninstalled()existed!You're absolutely right. That simplifies things a lot. Thanks :)Nope, that still doesn't solve the problem :/ The code for this must live in the specific storage back-end, because of
. We can't simply perform DB queries, because then we break this abstraction…
Comment #8
dave reidYes, we would need both parts, but at least each module wouldn't have to implement hook_uninstall to remove file usage.
Comment #9
swentel commented#5 sure - I know there's a gap in the cleaning up of files, I've seen that a lot in D7 sites. However, I thought that another module could also re-use a file from a module that's disabled, but looking at the api, it seems like we're actually safe. So carry on .. :)
Comment #10
wim leers#8: okay, agreed :)
Comment #11
wim leersHere we go.
Comment #13
wim leers#11: module_uninstall_delete_file_usage-2060405-11.patch queued for re-testing.
Comment #14
spleshkaGood catch! Patch looks good to me, mark its as RTBC.
Comment #15
spleshkaOh, forgot to change issue status.
Comment #16
wim leersThanks :)
But shouldn't there also be test coverage for File module's new
hook_modules_uninstalled()hook? I didn't add that yet because I wasn't sure what the best way to do that would be.Comment #17
spleshkaI thought that your test covers functionality in
hook_modules_uninstalled()Comment #18
wim leersYes, the test covers functionality used by
file_modules_uninstalled(), but notfile_modules_uninstalled()itself. I also think it should be sufficient, but I've seen lots of disagreements lately on what needs test coverage and what not, hence I'm asking.Comment #19
spleshkaThis hook covered by tests in system_test_modules_installed() and module_test_modules_uninstalled(), all other modules don't provide test coverage for it. So it seems that no needs in that.
Comment #20
dries commentedI reviewed this patch and it looks good. It looks like it needs a re-roll though. Asking for a re-test.
Comment #21
dries commented#11: module_uninstall_delete_file_usage-2060405-11.patch queued for re-testing.
Comment #22
alexpott#11: module_uninstall_delete_file_usage-2060405-11.patch queued for re-testing.
Comment #23
alexpottThis got committed in http://drupalcode.org/project/drupal.git/commitdiff/a567be3ee03182a7ef09...
I reverted in http://drupalcode.org/project/drupal.git/commitdiff/4ca797aa8c152d7b6a54...
Comment #25
wim leersStraight reroll of #11, to apply cleanly.
Comment #26
wim leersBack to RTBC as per #15 and #20.
Comment #27
alexpottCommitted 858674f and pushed to 8.x. Thanks!
Fixed spelling mistake during commit
Comment #28
wim leersHeh … thanks :)
Comment #29
berdirNot exactly sure what the goal of this issue is/was :)
Only *temporary* files with no usage are deleted. And files are marked temporary implicitly when all usages to it are deleted in FileUsageBase::delete(). The patch removes the file_usage entries but obviously doesn't mark them as temporary, so they will stick around forever, without usage information.
Some other things to consider:
- It's not possible to uninstall image.module when there are still any image fields around, removing image fields should also remove the corresponding usages correctly, so the situation for image.module is quite different.
- the file usage stuff used to be in system.module, which couldn't be uninstalled. Now you can uninstall file.module, which means it drops all database tables but doesn't actually delete the files...
- What happens with the embed tags in the content? Unlike image.module, you can uninstall editor.module and they will still be around. If the filter is enabled for some text formats, it will right now explode, with an RTBC issue the behavior will change to not print anything and output watchdog errors, after re-saving the format it will probably just print them out.
I have no idea how to actually solve this. Given the last point, you could argue that the file usage is in fact still there ;)
Comment #30
wim leersPrecisely what the title says: allow modules to declare they're no longer around and hence clean up their corresponding file usage counters :)
You're right that
deleteByModule()which was introduced here deviates fromdelete(), but sadly nobody noticed that :(Of course, calling this for every file being unreferenced because a module is being uninstalled is again equally unscalable as the original issue. I think the whole system is designed backwards: a file should not be explicitly marked as temporary or permanent; rather, a file should be implied to be permanent if there's >0 usages!
Except that no filter is used for #1932652: Add image uploading to WYSIWYGs through editor.module, so all those consequences won't actually happen. A review of that issue is definitely welcome :)
Comment #31
berdirWell, the system wasn't really designed, file status existed long before the file usage API did I think :) In 7.x, you have to *manually* set the file status when removing/adding file usages. Took me a lot of time & arguments to automate that process in 8.x.
The file usage API right now is kind of optional but not really, when you use the file_managed element #type then it will fail validation when trying to re-save something when there's still file usages.
And the file and file usage tables are now two detached pluggable systems, it's not possible and never would have been performant to replace the status column with an implicit check, this information needs to be materialized. Changing is I guess too much now.
The only idea that I have right now would be that we don't delete immediately (because you can't query something that's not there) but only initiate a process that will then do the cleanup, two possible ways, not sure if they will actually work.
- Instead of deleting the records, set the count to 0. Then implement some code in file_cron() that will look for those and delete them separately, in batches. Would need the ability to search for these records and keep the API introduced here.
- Use the queue API to set-off a self-re-enqueueing process that will search by module, delete N records and if there are more than that, re-enqueue itself. Doesn't need deleteByModule(), only findByModule()
Or, we could of course also remove the ability to uninstall modules :)
Re-opening for now, still not sure if it should be reverted or not.
Comment #32
damien tournoud commentedOne possible easy way out:
Is there a good reason for this? If we delete all files with no usage, we are good.
Comment #33
berdirWe have no way to query that.
What we do is query for temporary files with a changed date older than 6h (soon configurable), load 100 of them, check if they have any usages, if yes, log a watchdog and skip, otherwise delete.
We can't query for files without usages, because they aren't listed in the file_usage table. And we can't join those two tables as they are both pluggable now.
Comment #34
damien tournoud commented@Berdir, any reason the file usage service is not a field? Inventing our own storage for everything is so 1999.
Comment #35
berdirWell reason, I guess file usage was added be before files were entities and they're still not fieldable in core, although that's just a flag. Making it a field sounds like a nice idea but too late for that :)
Comment #36
damien tournoud commentedIt's not too late for anything. Fixing design issues requires breaking things. That's normal and expected. The API freeze was just premature anyway, given the very sad current state of core.
Comment #37
alexpottReverted with commit 0e47ce3 and pushed to 8.x.
We still need a solution but there is no point leaving a broken solution in.
Comment #38
dave reidComment #39
jhedstromThe issue summary needs updating given that parts of this were committed.
Comment #40
slashrsm commentedComment #41
slashrsm commentedComment #45
gábor hojtsyRemove obsolete media tag.
Comment #55
catch