jrnl/jrnl/upgrade.py
Jonathan Wren fcc8d8e3fa
Rework Encryption to enable future support of other encryption methods (#1602)
- initial pass through to rework encryption into separate module
- little more cleanup
- rename function, fix some linting issues
- more cleaning
- fix password bug in encryption
- fix linting issue
- more cleanup
- move prompt into prompt.py
- more cleanup
- update the upgrade process for new encryption classes
- general cleanup
- turn into enum instead of strings
- store status code so tests don't fail
- standardize the load and store methods in journals
- get rid of old PlainJournal class
- typing cleanup
- more cleanup
- format
- fix linting issue
- Fix obscure Windows line ending issue with decode
  See https://bugs.python.org/issue40863
- fix for python 3.11
- add more typing
- don't use class variables because that's not what we want
- fix more type hints
- jrnlv1 encryption doesn't support encryption anymore (it's deprecated)
- keep logic for password attemps inside the class that uses it
- take out old line of code
- add some more logging
- update logging statements
- clean up logging statements
- run linters
- fix typo
- Fix for new test from develop branch
  There was a new test added for re-encrypting a journal. This updates the
  refactor to match the old (previously untested) behavior of jrnl.

Co-authored-by: Micah Jerome Ellison <micah.jerome.ellison@gmail.com>
2022-11-19 13:39:39 -08:00

211 lines
6 KiB
Python

# Copyright © 2012-2022 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html
import logging
import os
from jrnl import Journal
from jrnl import __version__
from jrnl.config import is_config_json
from jrnl.config import load_config
from jrnl.config import scope_config
from jrnl.exception import JrnlException
from jrnl.messages import Message
from jrnl.messages import MsgStyle
from jrnl.messages import MsgText
from jrnl.output import print_msg
from jrnl.output import print_msgs
from jrnl.path import expand_path
from jrnl.prompt import yesno
def backup(filename: str, binary: bool = False):
filename = expand_path(filename)
try:
with open(filename, "rb" if binary else "r") as original:
contents = original.read()
with open(filename + ".backup", "wb" if binary else "w") as backup:
backup.write(contents)
print_msg(
Message(
MsgText.BackupCreated, MsgStyle.NORMAL, {"filename": "filename.backup"}
)
)
except FileNotFoundError:
print_msg(Message(MsgText.DoesNotExist, MsgStyle.WARNING, {"name": filename}))
cont = yesno(f"\nCreate {filename}?", default=False)
if not cont:
raise JrnlException(Message(MsgText.UpgradeAborted, MsgStyle.WARNING))
def check_exists(path: str) -> bool:
"""
Checks if a given path exists.
"""
return os.path.exists(path)
def upgrade_jrnl(config_path: str) -> None:
config = load_config(config_path)
print_msg(Message(MsgText.WelcomeToJrnl, MsgStyle.NORMAL, {"version": __version__}))
encrypted_journals = {}
plain_journals = {}
other_journals = {}
all_journals = []
for journal_name, journal_conf in config["journals"].items():
if isinstance(journal_conf, dict):
path = expand_path(journal_conf.get("journal"))
encrypt = journal_conf.get("encrypt")
else:
encrypt = config.get("encrypt")
path = expand_path(journal_conf)
if os.path.exists(path):
path = os.path.expanduser(path)
else:
print_msg(Message(MsgText.DoesNotExist, MsgStyle.ERROR, {"name": path}))
continue
if encrypt:
encrypted_journals[journal_name] = path
elif os.path.isdir(path):
other_journals[journal_name] = path
else:
plain_journals[journal_name] = path
kwargs = {
# longest journal name
"pad": max([len(journal) for journal in config["journals"]]),
}
_print_journal_summary(
journals=encrypted_journals,
header=Message(
MsgText.JournalsToUpgrade,
params={
"version": __version__,
},
),
**kwargs,
)
_print_journal_summary(
journals=plain_journals,
header=Message(
MsgText.JournalsToUpgrade,
params={
"version": __version__,
},
),
**kwargs,
)
_print_journal_summary(
journals=other_journals,
header=Message(MsgText.JournalsToIgnore),
**kwargs,
)
cont = yesno(Message(MsgText.ContinueUpgrade), default=False)
if not cont:
raise JrnlException(Message(MsgText.UpgradeAborted, MsgStyle.WARNING))
for journal_name, path in encrypted_journals.items():
print_msg(
Message(
MsgText.UpgradingJournal,
params={
"journal_name": journal_name,
"path": path,
},
)
)
backup(path, binary=True)
old_journal = Journal.open_journal(
journal_name, scope_config(config, journal_name), legacy=True
)
logging.debug(f"Clearing encryption method for '{journal_name}' journal")
# Update the encryption method
new_journal = Journal.Journal.from_journal(old_journal)
new_journal.config["encrypt"] = "jrnlv2"
new_journal._get_encryption_method()
# Copy over password (jrnlv1 only supported password-based encryption)
new_journal.encryption_method.password = old_journal.encryption_method.password
all_journals.append(new_journal)
for journal_name, path in plain_journals.items():
print_msg(
Message(
MsgText.UpgradingJournal,
params={
"journal_name": journal_name,
"path": path,
},
)
)
backup(path)
old_journal = Journal.open_journal(
journal_name, scope_config(config, journal_name), legacy=True
)
all_journals.append(Journal.Journal.from_journal(old_journal))
# loop through lists to validate
failed_journals = [j for j in all_journals if not j.validate_parsing()]
if len(failed_journals) > 0:
raise JrnlException(
Message(MsgText.AbortingUpgrade, MsgStyle.WARNING),
Message(
MsgText.JournalFailedUpgrade,
MsgStyle.ERROR,
{
"s": "s" if len(failed_journals) > 1 else "",
"failed_journals": "\n".join(j.name for j in failed_journals),
},
),
)
# write all journals - or - don't
for j in all_journals:
j.write()
print_msg(Message(MsgText.UpgradingConfig, MsgStyle.NORMAL))
backup(config_path)
print_msg(Message(MsgText.AllDoneUpgrade, MsgStyle.NORMAL))
def is_old_version(config_path: str) -> bool:
return is_config_json(config_path)
def _print_journal_summary(journals: dict, header: Message, pad: int) -> None:
if not journals:
return
msgs = [header]
for journal, path in journals.items():
msgs.append(
Message(
MsgText.PaddedJournalName,
params={
"journal_name": journal,
"path": path,
"pad": pad,
},
)
)
print_msgs(msgs)