-
-
Notifications
You must be signed in to change notification settings - Fork 43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generate a unique crowdanki_uuid
for each deck and subdeck during every export
#164
Comments
Specific solutions/hacks
I think it'd be sufficient for the deck uuids not to be stored in the database (or immediately removed after being added), which should be straightforward to do. You can manually remove the uuids using the debug console (Ctrl+Shift+;): Debug console scriptfor deck in mw.col.decks.all():
if deck["name"].startswith("NAME_OF_TOP_LEVEL_DECK"):
if "crowdanki_uuid" in deck:
print(f'Deleting UUID {deck["crowdanki_uuid"]} for deck {deck["name"]}')
del deck["crowdanki_uuid"]
mw.col.decks.save(deck) or automatically before every export with this patch: CrowdAnki patch--- a/crowd_anki/export/anki_exporter_wrapper.py
+++ b/crowd_anki/export/anki_exporter_wrapper.py
@@ -50,6 +50,7 @@ def exportInto(self, directory_path):
# https://github.com/Stvad/CrowdAnki/wiki/Workarounds-%E2%80%94-Duplicate-note-model-uuids.
disambiguate_note_model_uuids(self.collection)
+ remove_deck_uuids(self.collection)
# .parent because we receive name with random numbers at the end (hacking around internals of Anki) :(
export_path = Path(directory_path).parent
self.anki_json_exporter.export_to_directory(deck, export_path, self.includeMedia,
@@ -66,3 +67,13 @@ def exporters_hook(exporters_list):
exporter_id = get_exporter_id(AnkiJsonExporterWrapper)
if exporter_id not in exporters_list:
exporters_list.append(exporter_id)
+
+
+def remove_deck_uuids(collection):
+ for deck in collection.decks.all():
+ # I assume you want this to happen only for a certain deck (and all its subdecks)
+ if deck["name"].startswith("NAME_OF_RELEVANT_TOP_LEVEL_DECK"):
+ if "crowdanki_uuid" in deck:
+ print(f'Deleting UUID {deck["crowdanki_uuid"]} for deck {deck["name"]}')
+ del deck["crowdanki_uuid"]
+ collection.decks.save(deck) The advantage of deleting the UUIDs just before exporting (rather than, say, after exporting) is that the deck UUIDs will stay the same between exports. In particular, if somebody sends you an update to the deck, you'll still be able to import it, without the notes changing deck etc.
There aren't really any compatibility issues. Any generated random string should work... If you want to follow convention (ideally please do! :)) then we're using uuids generated with The slight issue is that if you, yourself, import such a deck (with randomised uuids), then all your notes will be moved to newly created decks and sub-decks (just like what would happen for all other users of your deck :)). AlternativesYou could have a "deleted" sub-deck and when you want to delete a note, instead move it into that sub-deck. This is obviously annoying, though... I hope the above helps! :) General solutionsObviously, the issue with notes being deleted from a deck is a general one. Long-term it'd be nice for CrowdAnki to have an option to deal with upstream-deleted notes. For instance, we could, on import:
(I personally strongly prefer 2.) Finding notes in a deck which aren't in the currently imported CrowdAnki deck is pretty straightforward. The tricky part is distinguishing notes that were deleted "upstream" from "personal" notes that were added by the given user. AFAICT there are two main options to resolve this:
I personally lean towards 2, but the approaches are complementary (one focuses on the upstream-deleted notes, the other focuses on the personal ones), so we could, in principle, use both — have an optional |
Another options is a "Hard sync" checkbox on import, which would move any cards not in the imported deck(s), but that exist in your local decks, into this "deleted" / "unknown notes" deck, so that the user may delete or move back at their own choosing. Would be easy to implement too, just:
Just a thought about how to accomplish this 👍 Edit: Oh I see you general said this later on. My bad 😅 |
Tagging is surely the way to go. It is clear and obvious, especially for many users all syncing their cards together (rather than a user syncing to one shared deck). |
@aplaice case in point, that's my intended end goal!
I don't know much about how Anki modules work unfortunately, just some basic Python to get me by here and there (it is on my bucket list of things to learn though). But if I just replace the
@ohare93 Truly, it is in retrospect. Before making my big release, I had a single 4000 card deck with extensive tagging and zero subdecks. Problem is, most users who find my deck are usually Anki noobs, and I constantly got DMs along the line of "How do I select only specific topics to study from your deck!?". Many didn't even know that the Anki Browse pane existed. So, I figured I would just convert all the tagging and nested tags to nested decks instead, which would mean beginners could just scroll the sub decks and just click and review the topics they needed, and pros could make filtered decks as usual because they of course know how Anki works. I'm painfully aware that Anki isn't very sub deck friendly, because in all fairness tags were the intended way - but this is a compromise I made to hopefully make using my deck easier and faster for end users. :D |
YES PLEASE! I personally prefer method 2) too, |
That's a general python module (not anki-specific).
Yes, that will work. However, very simple strings like The following quickly/sloppily written python script should carry out the uuid change, with Python script (updated as below)import json from uuid import uuid1
def regenerate_uuid_in_deck(deck_dict): deck_dict["crowdanki_uuid"] =
str(uuid1())
def regenerate_uuid_in_subdecks(deck_dict): children =
deck_dict["children"] for child in children:
regenerate_uuid_in_deck(child) regenerate_uuid_in_subdecks(child)
with open("deck.json") as f: deck_dict = json.load(f)
regenerate_uuid_in_subdecks(deck_dict)
with open("deck_modified.json", "w") as f:
json.dump(deck_dict, f, indent=4, sort_keys=True, ensure_ascii=False) It doesn't change the uuid of the top-level deck, since AFAIU you don't want the top-level deck to be moved, just all its subdecks. Please feel free to modify it as you see fit or complain if it doesn't work. :) It seems that we all agree that tags are the way to go! (On second thought, unless and until we properly set up handling/tagging of "personal" notes, maybe a better tag name might be |
I was actually thinking that this would be a good problem for Brain Brew to solve: just use tags, but have an export option that moves the tags into subdecks. So the tag "Science::Physics::Forces::Gravity" would just be put in a subdeck 4 levels down, but "Science::Physics" would only be two levels down. Then you can "have your cake and eat it too" in that one can offer a deck based on tags or subdecks, all without actually touching subdecks yourself 😁 Of course having this feature in CrowdAnki would be a solution too! But I doubt that would happen. |
I had meant tags for "marking" deleted/missing notes. Thinking about how to allow sub-decks for people who prefer sub-decks to tags you're far ahead of me! :)
How would you deal with notes with multiple tags, though? Have a list of special, mutually-exclusive tags? In any case, it's a very interesting idea! :) |
Ah right, of course 😁
First come first serve? Throw an error? 😆
👍 |
@aplaice That would solve my use case elegantly, but seems like it removes all line breaks in the output Here's the before and after (with the extension changed to |
No. The script was just quickly/sloppily written. :D You can use import json from uuid import uuid1
def regenerate_uuid_in_deck(deck_dict): deck_dict["crowdanki_uuid"] =
str(uuid1())
def regenerate_uuid_in_subdecks(deck_dict): children =
deck_dict["children"] for child in children:
regenerate_uuid_in_deck(child) regenerate_uuid_in_subdecks(child)
with open("deck.json") as f: deck_dict = json.load(f)
regenerate_uuid_in_subdecks(deck_dict)
with open("deck_modified.json", "w") as f:
json.dump(deck_dict, f, indent=4, sort_keys=True, ensure_ascii=False)
|
@aplaice YES, the script helped me get the perfect end result! So, here's how an update looks on the user's end :
Perfect and elegant. I'm grateful for your help and support, just an amazing plugin all around! Feel free to close this issue. |
This is not a feature request, but is rather a niche use case I need to understand for publishing updates for a massive deck repository I manage; my apologies for the long read.
Context (why I can't use tags instead of subdecks)
.apkg
import or CrowdAnki.The Problem I need to solve
10,000
cards, and in an update I delete some useless stuff and it contains9,000
cards. Then the user will still have those1,000
deleted cards in their previous positions without knowing that they're supposed to be deleted.The very niche solution I'm trying to find
uuid
for each deck.A
. If I rename the deckA
toA_renamed
, and hit export, theuuid
still remains the same. But if I renameA
toA_renamed
, deleteA
, make a new deck with the same nameA
, move the cards fromA_renamed
toA
, and exportA
via CrowdAnki, a newuuid
is generated in the.json
.uuid
for each and every export? This would create an entirely new duplicate of the deck, pull the9,000
cards from the older version and rearrange them in any new modified subdeck arrangement, and the older version would have the remaining1,000
cards left over. The end user can delete the older deck at their discretion.uuid
s in the exported.json
to achieve the same effect, but then are there any naming convention issues or compatibility issues I might have to contend with?The text was updated successfully, but these errors were encountered: