mirror of
https://github.com/olivierkes/manuskript.git
synced 2024-06-16 01:44:34 +12:00
Removes deleted character's files when saving
This commit is contained in:
parent
97903b2781
commit
da5bfb8951
|
@ -66,6 +66,10 @@ def slugify(name):
|
||||||
return newName
|
return newName
|
||||||
|
|
||||||
|
|
||||||
|
def log(*args):
|
||||||
|
print(" ".join(str(a) for a in args))
|
||||||
|
|
||||||
|
|
||||||
def saveProject(zip=None):
|
def saveProject(zip=None):
|
||||||
"""
|
"""
|
||||||
Saves the project. If zip is False, the project is saved as a multitude of plain-text files for the most parts
|
Saves the project. If zip is False, the project is saved as a multitude of plain-text files for the most parts
|
||||||
|
@ -80,12 +84,14 @@ def saveProject(zip=None):
|
||||||
zip = False
|
zip = False
|
||||||
# Fixme
|
# Fixme
|
||||||
|
|
||||||
print("\n\n", "Saving to:", "zip" if zip else "folder")
|
log("\n\nSaving to:", "zip" if zip else "folder")
|
||||||
|
|
||||||
# List of files to be written
|
# List of files to be written
|
||||||
files = []
|
files = []
|
||||||
# List of files to be removed
|
# List of files to be removed
|
||||||
removes = []
|
removes = []
|
||||||
|
# List of files to be moved
|
||||||
|
moves = []
|
||||||
|
|
||||||
mw = mainWindow()
|
mw = mainWindow()
|
||||||
|
|
||||||
|
@ -117,6 +123,7 @@ def saveProject(zip=None):
|
||||||
)
|
)
|
||||||
files.append((path, content))
|
files.append((path, content))
|
||||||
|
|
||||||
|
####################################################################################################################
|
||||||
# Summary
|
# Summary
|
||||||
# In plain text, in summary.txt
|
# In plain text, in summary.txt
|
||||||
|
|
||||||
|
@ -135,6 +142,7 @@ def saveProject(zip=None):
|
||||||
|
|
||||||
files.append((path, content))
|
files.append((path, content))
|
||||||
|
|
||||||
|
####################################################################################################################
|
||||||
# Label & Status
|
# Label & Status
|
||||||
# In plain text
|
# In plain text
|
||||||
|
|
||||||
|
@ -162,7 +170,8 @@ def saveProject(zip=None):
|
||||||
|
|
||||||
files.append((path, content))
|
files.append((path, content))
|
||||||
|
|
||||||
# Characters (self.mdlCharacter)
|
####################################################################################################################
|
||||||
|
# Characters
|
||||||
# In a character folder
|
# In a character folder
|
||||||
|
|
||||||
path = os.path.join("characters", "{name}.txt")
|
path = os.path.join("characters", "{name}.txt")
|
||||||
|
@ -179,7 +188,11 @@ def saveProject(zip=None):
|
||||||
(Character.notes, "Notes"),
|
(Character.notes, "Notes"),
|
||||||
]
|
]
|
||||||
mdl = mw.mdlCharacter
|
mdl = mw.mdlCharacter
|
||||||
|
|
||||||
|
# Review characters
|
||||||
for c in mdl.characters:
|
for c in mdl.characters:
|
||||||
|
|
||||||
|
# Generates file's content
|
||||||
content = ""
|
content = ""
|
||||||
for m, name in _map:
|
for m, name in _map:
|
||||||
val = mdl.data(c.index(m.value)).strip()
|
val = mdl.data(c.index(m.value)).strip()
|
||||||
|
@ -189,28 +202,46 @@ def saveProject(zip=None):
|
||||||
for info in c.infos:
|
for info in c.infos:
|
||||||
content += formatMetaData(info.description, info.value, 20)
|
content += formatMetaData(info.description, info.value, 20)
|
||||||
|
|
||||||
|
# generate file's path
|
||||||
cpath = path.format(name="{ID}-{slugName}".format(
|
cpath = path.format(name="{ID}-{slugName}".format(
|
||||||
ID=c.ID(),
|
ID=c.ID(),
|
||||||
slugName=slugify(c.name())
|
slugName=slugify(c.name())
|
||||||
))
|
))
|
||||||
# Has the character been renamed?
|
|
||||||
# If so, we remove the old file (if not zipped)
|
|
||||||
if c.lastPath and cpath != c.lastPath:
|
|
||||||
removes.append(c.lastPath)
|
|
||||||
c.lastPath = cpath
|
|
||||||
files.append((
|
|
||||||
cpath,
|
|
||||||
content))
|
|
||||||
|
|
||||||
|
# Has the character been renamed?
|
||||||
|
if c.lastPath and cpath != c.lastPath:
|
||||||
|
moves.append((c.lastPath, cpath))
|
||||||
|
|
||||||
|
# Update character's path
|
||||||
|
c.lastPath = cpath
|
||||||
|
|
||||||
|
files.append((cpath, content))
|
||||||
|
|
||||||
|
# List removed characters
|
||||||
|
for c in mdl.removed:
|
||||||
|
# generate file's path
|
||||||
|
cpath = path.format(name="{ID}-{slugName}".format(
|
||||||
|
ID=c.ID(),
|
||||||
|
slugName=slugify(c.name())
|
||||||
|
))
|
||||||
|
|
||||||
|
# Mark for removal
|
||||||
|
removes.append(cpath)
|
||||||
|
|
||||||
|
mdl.removed.clear()
|
||||||
|
|
||||||
|
####################################################################################################################
|
||||||
# Texts
|
# Texts
|
||||||
# In an outline folder
|
# In an outline folder
|
||||||
|
|
||||||
mdl = mw.mdlOutline
|
mdl = mw.mdlOutline
|
||||||
f, r = exportOutlineItem(mdl.rootItem)
|
f, m, r = exportOutlineItem(mdl.rootItem)
|
||||||
files += f
|
files += f
|
||||||
|
moves += m
|
||||||
removes += r
|
removes += r
|
||||||
|
|
||||||
# World (mw.mdlWorld)
|
####################################################################################################################
|
||||||
|
# World
|
||||||
# Either in an XML file, or in lots of plain texts?
|
# Either in an XML file, or in lots of plain texts?
|
||||||
# More probably text, since there might be writing done in third-party.
|
# More probably text, since there might be writing done in third-party.
|
||||||
|
|
||||||
|
@ -224,6 +255,7 @@ def saveProject(zip=None):
|
||||||
content = ET.tostring(root, encoding="UTF-8", xml_declaration=True, pretty_print=True)
|
content = ET.tostring(root, encoding="UTF-8", xml_declaration=True, pretty_print=True)
|
||||||
files.append((path, content))
|
files.append((path, content))
|
||||||
|
|
||||||
|
####################################################################################################################
|
||||||
# Plots (mw.mdlPlots)
|
# Plots (mw.mdlPlots)
|
||||||
# Either in XML or lots of plain texts?
|
# Either in XML or lots of plain texts?
|
||||||
# More probably XML since there is not really a lot if writing to do (third-party)
|
# More probably XML since there is not really a lot if writing to do (third-party)
|
||||||
|
@ -236,15 +268,19 @@ def saveProject(zip=None):
|
||||||
content = ET.tostring(root, encoding="UTF-8", xml_declaration=True, pretty_print=True)
|
content = ET.tostring(root, encoding="UTF-8", xml_declaration=True, pretty_print=True)
|
||||||
files.append((path, content))
|
files.append((path, content))
|
||||||
|
|
||||||
|
####################################################################################################################
|
||||||
# Settings
|
# Settings
|
||||||
# Saved in readable text (json) for easier versionning. But they mustn't be shared, it seems.
|
# Saved in readable text (json) for easier versionning. But they mustn't be shared, it seems.
|
||||||
# Maybe include them only if zipped?
|
# Maybe include them only if zipped?
|
||||||
# Well, for now, we keep them here...
|
# Well, for now, we keep them here...
|
||||||
|
|
||||||
files.append(("settings.txt", settings.save(protocol=0)))
|
files.append(("settings.txt", settings.save(protocol=0)))
|
||||||
|
|
||||||
project = mw.currentProject
|
project = mw.currentProject
|
||||||
|
|
||||||
|
####################################################################################################################
|
||||||
# Save to zip
|
# Save to zip
|
||||||
|
|
||||||
if zip:
|
if zip:
|
||||||
project = os.path.join(
|
project = os.path.join(
|
||||||
os.path.dirname(project),
|
os.path.dirname(project),
|
||||||
|
@ -258,41 +294,58 @@ def saveProject(zip=None):
|
||||||
|
|
||||||
zf.close()
|
zf.close()
|
||||||
|
|
||||||
|
####################################################################################################################
|
||||||
# Save to plain text
|
# Save to plain text
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
|
# Project path
|
||||||
dir = os.path.dirname(project)
|
dir = os.path.dirname(project)
|
||||||
|
|
||||||
|
# Folder containing file: name of the project file (without .msk extension)
|
||||||
folder = os.path.splitext(os.path.basename(project))[0]
|
folder = os.path.splitext(os.path.basename(project))[0]
|
||||||
print("\nSaving to folder", folder)
|
|
||||||
|
# Debug
|
||||||
|
log("\nSaving to folder", folder)
|
||||||
|
|
||||||
|
# Moving files that have been renamed
|
||||||
|
for old, new in moves:
|
||||||
|
|
||||||
|
# Get full path
|
||||||
|
oldPath = os.path.join(dir, folder, old)
|
||||||
|
newPath = os.path.join(dir, folder, new)
|
||||||
|
|
||||||
|
# Move the old file to the new place
|
||||||
|
os.replace(oldPath, newPath)
|
||||||
|
log("* Renaming {} to {}".format(old, new))
|
||||||
|
|
||||||
|
# Update cache
|
||||||
|
if old in cache:
|
||||||
|
cache[new] = cache.pop(old)
|
||||||
|
|
||||||
for path in removes:
|
for path in removes:
|
||||||
if path not in [p for p,c in files]:
|
if path not in [p for p,c in files]:
|
||||||
filename = os.path.join(dir, folder, path)
|
filename = os.path.join(dir, folder, path)
|
||||||
print("* Removing", filename)
|
log("* Removing", path)
|
||||||
|
|
||||||
if os.path.isdir(filename):
|
if os.path.isdir(filename):
|
||||||
shutil.rmtree(filename)
|
shutil.rmtree(filename)
|
||||||
# FIXME: when deleting a folder, there are still files in "removes".
|
|
||||||
|
|
||||||
# FIXME: if user copied custom files in the directory, they will be lost.
|
|
||||||
# need to find a way to rename instead of remove.
|
|
||||||
|
|
||||||
# FIXME: items removed have to be removed (not just the renamed)
|
|
||||||
|
|
||||||
else: # elif os.path.exists(filename)
|
else: # elif os.path.exists(filename)
|
||||||
os.remove(filename)
|
os.remove(filename)
|
||||||
|
|
||||||
|
# Clear cache
|
||||||
cache.pop(path, 0)
|
cache.pop(path, 0)
|
||||||
|
|
||||||
for path, content in files:
|
for path, content in files:
|
||||||
filename = os.path.join(dir, folder, path)
|
filename = os.path.join(dir, folder, path)
|
||||||
os.makedirs(os.path.dirname(filename), exist_ok=True)
|
os.makedirs(os.path.dirname(filename), exist_ok=True)
|
||||||
# print("* Saving file", filename)
|
|
||||||
|
|
||||||
# TODO: the first time it saves, it will overwrite everything, since it's not yet in cache.
|
# TODO: the first time it saves, it will overwrite everything, since it's not yet in cache.
|
||||||
# Or we have to cache while loading.
|
# Or we have to cache while loading.
|
||||||
|
|
||||||
if not path in cache or cache[path] != content:
|
if not path in cache or cache[path] != content:
|
||||||
print("* Saving file", filename)
|
log("* Writing file", path)
|
||||||
print(" Not in cache or changed: we write")
|
|
||||||
mode = "w"+ ("b" if type(content) == bytes else "")
|
mode = "w"+ ("b" if type(content) == bytes else "")
|
||||||
with open(filename, mode) as f:
|
with open(filename, mode) as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
|
@ -300,7 +353,7 @@ def saveProject(zip=None):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
# print(" In cache, and identical. Do nothing.")
|
# log(" In cache, and identical. Do nothing.")
|
||||||
|
|
||||||
|
|
||||||
def addWorldItem(root, mdl, parent=QModelIndex()):
|
def addWorldItem(root, mdl, parent=QModelIndex()):
|
||||||
|
@ -393,6 +446,7 @@ def exportOutlineItem(root):
|
||||||
"""
|
"""
|
||||||
Takes an outline item, and returns two lists:
|
Takes an outline item, and returns two lists:
|
||||||
1. of (`filename`, `content`), representing the whole tree of files to be written, in multimarkdown.
|
1. of (`filename`, `content`), representing the whole tree of files to be written, in multimarkdown.
|
||||||
|
3. of (`filename`, `filename`) listing files to be moved
|
||||||
2. of `filename`, representing files to be removed.
|
2. of `filename`, representing files to be removed.
|
||||||
|
|
||||||
@param root: OutlineItem
|
@param root: OutlineItem
|
||||||
|
@ -400,6 +454,7 @@ def exportOutlineItem(root):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
files = []
|
files = []
|
||||||
|
moves = []
|
||||||
removes = []
|
removes = []
|
||||||
|
|
||||||
path = "outline"
|
path = "outline"
|
||||||
|
@ -410,16 +465,15 @@ def exportOutlineItem(root):
|
||||||
k += 1
|
k += 1
|
||||||
|
|
||||||
# Has the item been renamed?
|
# Has the item been renamed?
|
||||||
# If so, we mark the old file for removal
|
|
||||||
if "Herod_dies" in spath:
|
|
||||||
print(child.title(), spath, "<==", child.lastPath)
|
|
||||||
if child.lastPath and spath != child.lastPath:
|
if child.lastPath and spath != child.lastPath:
|
||||||
removes.append(child.lastPath)
|
moves.append((child.lastPath, spath))
|
||||||
print(child.title(), "has been renamed (", child.lastPath, " → ", spath, ")")
|
log(child.title(), "has been renamed (", child.lastPath, " → ", spath, ")")
|
||||||
print(" → We remove:", child.lastPath)
|
log(" → We mark for moving:", child.lastPath)
|
||||||
|
|
||||||
|
# Updates item last's path
|
||||||
child.lastPath = spath
|
child.lastPath = spath
|
||||||
|
|
||||||
|
# Generating content
|
||||||
if child.type() == "folder":
|
if child.type() == "folder":
|
||||||
fpath = os.path.join(spath, "folder.txt")
|
fpath = os.path.join(spath, "folder.txt")
|
||||||
content = outlineToMMD(child)
|
content = outlineToMMD(child)
|
||||||
|
@ -431,16 +485,18 @@ def exportOutlineItem(root):
|
||||||
|
|
||||||
elif child.type() in ["html"]:
|
elif child.type() in ["html"]:
|
||||||
# FIXME: Convert first
|
# FIXME: Convert first
|
||||||
pass
|
content = outlineToMMD(child)
|
||||||
|
files.append((spath, content))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print("Unknown type")
|
log("Unknown type")
|
||||||
|
|
||||||
f, r = exportOutlineItem(child)
|
f, m, r = exportOutlineItem(child)
|
||||||
files += f
|
files += f
|
||||||
|
moves += m
|
||||||
removes += r
|
removes += r
|
||||||
|
|
||||||
return files, removes
|
return files, moves, removes
|
||||||
|
|
||||||
def outlineItemPath(item):
|
def outlineItemPath(item):
|
||||||
# Root item
|
# Root item
|
||||||
|
@ -472,7 +528,7 @@ def outlineToMMD(item):
|
||||||
content += item.data(Outline.text.value)
|
content += item.data(Outline.text.value)
|
||||||
|
|
||||||
# Saving revisions
|
# Saving revisions
|
||||||
# TODO
|
# TODO: saving revisions?
|
||||||
# rev = item.revisions()
|
# rev = item.revisions()
|
||||||
# for r in rev:
|
# for r in rev:
|
||||||
# revItem = ET.Element("revision")
|
# revItem = ET.Element("revision")
|
||||||
|
|
|
@ -12,7 +12,11 @@ class characterModel(QAbstractItemModel):
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
QAbstractItemModel.__init__(self, parent)
|
QAbstractItemModel.__init__(self, parent)
|
||||||
|
|
||||||
|
# CharacterItems are stored in this list
|
||||||
self.characters = []
|
self.characters = []
|
||||||
|
# We keep track of removed character, so that when we save in multiple files, we can remove old character's
|
||||||
|
# files.
|
||||||
|
self.removed = []
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# QAbstractItemModel subclassed
|
# QAbstractItemModel subclassed
|
||||||
|
@ -170,7 +174,9 @@ class characterModel(QAbstractItemModel):
|
||||||
"""
|
"""
|
||||||
c = self.getCharacterByID(ID)
|
c = self.getCharacterByID(ID)
|
||||||
self.beginRemoveRows(QModelIndex(), self.characters.index(c), self.characters.index(c))
|
self.beginRemoveRows(QModelIndex(), self.characters.index(c), self.characters.index(c))
|
||||||
|
self.removed.append(c)
|
||||||
self.characters.remove(c)
|
self.characters.remove(c)
|
||||||
|
self.endRemoveRows()
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# CHARACTER INFOS
|
# CHARACTER INFOS
|
||||||
|
|
Loading…
Reference in a new issue