Skip to content

Commit 10f1995

Browse files
committed
Auto-Update fundamentally works
1 parent 5ceab76 commit 10f1995

2 files changed

Lines changed: 129 additions & 8 deletions

File tree

Pckgd/Pckgd.py

Lines changed: 116 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def parse_headers(self):
5656
if (self.typeId == 5 and line.strip().startswith('#')) or \
5757
(self.typeId == 4 and line.strip().startswith('--')):
5858
prefix_chars = 1 if self.typeId == 5 else 2
59+
line = line.strip()
5960
parts = line[prefix_chars:].split(':', 1)
6061
if len(parts) == 2:
6162
key = parts[0].strip()
@@ -91,8 +92,8 @@ def get_action_buttons(self):
9192

9293
if self.has_update_available():
9394
buttons.append("""
94-
<a href="PyScript/Pckgd?v=update&pkg={}" class="btn btn-primary">Update</a>
95-
""".format(self.filename))
95+
<a href="/PyScript/Pckgd?v=update&pkg={}" class="btn btn-primary">Update</a>
96+
""".format(self.filename_with_extension()))
9697

9798
return "\n".join(buttons)
9899

@@ -195,6 +196,8 @@ def set_header(body, key, value, type_id):
195196
new_lines.append(line)
196197
elif Pckgd.do_not_edit_demarcation in line:
197198
demarc_found = True # once we hit the demarcation, stop looking for headers
199+
else:
200+
new_lines.append(line)
198201

199202
if not header_found: # Doesn't exist, so needs to be inserted.
200203
insert_index = 0
@@ -316,6 +319,38 @@ def _save_meta_if_dirty():
316319
del meta['_dirty']
317320
model.WriteContentText("PckgdCache.json", json.dumps(meta, indent=2))
318321

322+
323+
def do_update(self, new_pckg):
324+
# Update the content in the system
325+
# If using demarcation, preserve anything above it (the "preamble").
326+
preamble = None
327+
new_body = new_pckg.body
328+
if Pckgd.do_not_edit_demarcation in self.body:
329+
preamble = self.body.split(Pckgd.do_not_edit_demarcation, 1)[0]
330+
331+
# Assemble new body with old preamble.
332+
if preamble is not None and Pckgd.do_not_edit_demarcation in new_pckg.body:
333+
new_body = new_pckg.body.split(Pckgd.do_not_edit_demarcation, 1)[-1].strip()
334+
335+
if preamble is not None:
336+
new_body = preamble + '\n' + ('#' if self.typeId == 5 else '--') + Pckgd.do_not_edit_demarcation + new_pckg.body
337+
338+
v = new_pckg.version
339+
340+
self.body = new_body
341+
342+
if "Version" in self.headers or "Version" in new_pckg.headers:
343+
new_body = Pckgd.set_header(new_pckg.body, 'Version', v, self.typeId)
344+
345+
if self.typeId == 5:
346+
model.WriteContentPython(self.filename, new_body)
347+
elif self.typeId == 4:
348+
model.WriteContentSql(self.filename, new_body)
349+
350+
# TODO update dependencies, as well.
351+
352+
353+
319354
def do_listing_view():
320355
model.Header = "Package Manager"
321356
model.Title = "Package Manager"
@@ -411,5 +446,83 @@ def do_listing_view():
411446
</style>
412447
""")
413448

449+
def do_update_view():
450+
model.Header = "Package Manager - Update Package"
451+
model.Title = "Package Manager - Update Package"
452+
453+
pkg_name = Data.pkg
454+
pkg = None
455+
for p in Pckgd.find_installed_packages():
456+
if p.filename_with_extension() == pkg_name:
457+
pkg = p
458+
break
459+
460+
if not pkg:
461+
print("<p><strong>Package not found: {}</strong></p>\n".format(pkg_name))
462+
return
463+
464+
print("<h2>Update Package: {}</h2>\n".format(pkg.name()))
465+
466+
update_source = pkg.get_update_source()
467+
if not update_source:
468+
print("<p><strong>No update source defined for this package.</strong></p>\n")
469+
return
470+
471+
remote_content = model.RestGet(update_source, {})
472+
if remote_content == "404: Not Found":
473+
print("<p><strong>Update source not found: {}</strong></p>\n".format(update_source))
474+
return
475+
476+
remote_pkg = Pckgd(pkg.typeId, pkg.filename, remote_content)
477+
478+
if remote_pkg.version == pkg.version:
479+
print("<p><strong>This package is already up to date.</strong></p>\n")
480+
return
481+
482+
# Perform the update
483+
try:
484+
pkg.do_update(remote_pkg)
485+
print("<p><strong>Package updated successfully to version {}.</strong></p>\n".format(remote_pkg.version))
486+
except Exception as e:
487+
print("<p><strong>Error updating package: {}</strong></p>\n".format(str(e)))
488+
489+
414490
if model.HttpMethod == "get" and Data.v == "":
415-
do_listing_view()
491+
do_listing_view()
492+
493+
494+
elif model.HttpMethod == "get" and Data.v == "update" and Data.pkg != "":
495+
do_update_view()
496+
497+
elif model.HttpMethod == "get" and Data.v == "test":
498+
model.Header = "Package Manager - Test"
499+
model.Title = "Package Manager - Test"
500+
501+
input = """
502+
# Pckgd
503+
# Title: Sample Package
504+
# Description: A sample package for testing.
505+
# Updates from: GitHub/ExampleUser/ExampleRepo/SamplePackage.py
506+
507+
somevar = 123
508+
509+
# this is preamble that should be preserved.
510+
511+
# ==============
512+
513+
# This is content that should be changed.
514+
515+
"""
516+
517+
p = Pckgd(5, "SamplePackage", input)
518+
print("<h2>Package: {}</h2>\n".format(p.name()))
519+
print("<pre>{}</pre>\n".format(json.dumps(p.headers, indent=2)))
520+
print("<p>Version: {}</p>\n".format(p.version))
521+
522+
new_input = Pckgd.set_header(input, "Version", "1.2.3", 5)
523+
524+
p2 = Pckgd(5, "SamplePackage", new_input)
525+
print("<h2>Updated Package: {}</h2>\n".format(p2.name()))
526+
print("<pre>{}</pre>\n".format(json.dumps(p2.headers, indent=2)))
527+
print("<p>Version: {}</p>\n".format(p2.version))
528+

Pckgd/README.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ be located in the first 100 lines of the script, and must be located immediately
1717
demarcation is used (explained below), the headers must be *above* the demarcation.
1818
The headers are:
1919

20-
- `# Pckgd` - Required. No value. Identifies the file as part of a Pckgd package. Without this header, the script won't be found in
21-
the query for packages.
20+
- `# Pckgd` - Required as the first of these headers. No value. Identifies the file as part of a Pckgd package, and
21+
indicates where these headers are in the file. Without this header, the script won't be found in the query for
22+
packages, and the headers won't be found in the file.
2223
- `# Updates from:` - Required for updates. An identifier of where updates should come from. Two types of values are allowed:
2324
- A URL to a text file: e.g. `# Updates from: https://example.com/mypackage-latest.txt`
2425
- A GitHub repository in the format `GitHub/username/repo/path/to/file.py`: e.g. `# Updates from: GitHub/TenthPres/TouchPointScripts/Pckgd/Pckgd.py`
@@ -28,7 +29,10 @@ The headers are:
2829
on. For example, if a python script depends on a SQL file, this header might be something like `# Depends on: MyScript.sql`.
2930
When dependencies are defined, they will be provided as part of installation, provided they are available in the same
3031
source and directory as the current file.
31-
- `# Version` - Optional. The current version of the script. If not provided, a hash will be generated on a per-file basis to determine if a new version is available.
32+
- `# Version:` - Optional. The current version of the script. Must contain hex characters and dots only (e.g. `1.0.0`, `2.1`, `2024.06.15`, `adcf1234`).
33+
- If not provided, a hash will be generated on a per-file basis to determine if a new version is available.
34+
- If a version is provided in the source file (e.g. on GitHub), only the version numbers will be compared, not the content of the script. Therefore, if you use this parameter, to make clients see a new update as being available, you *must* change the version number on the repository.
35+
- If you want every new update published on your repository to be treated as a new version, leave this header out.
3236
- `# License:` - Optional. The license under which the package is provided.
3337
- `# Author:` - Optional. The author of the package.
3438
- `# Header color:` - Optional. A hex color code (e.g. `#FF0000`) to use as the header color in the UI. If one is not provided, a color is generated from a hash of the file name.
@@ -53,9 +57,9 @@ A minimally-documented SQL file may have headers like this:
5357
-- Updates from: https://example.com/mypackage-latest.sql
5458
```
5559

56-
### "Stop editing" demarcation
60+
### "Stop Editing" Demarcation
5761

58-
Some scripts may require that some variables or constants be set for the script to work properly, particularly to
62+
Some scripts require some variables or constants set manually for the script to work properly, particularly to
5963
adapt the script to the specific needs of a given church. These variables must be near the top of the script, and
6064
should be separated from the rest of the script with a "stop editing" demarcation, like this:
6165

@@ -69,5 +73,9 @@ This is crucial for two reasons:
6973
2. When files are evaluated to see if updates are needed, only the content below this line is considered. This allows
7074
for customizations to exist and also not interfere with update detection.
7175

76+
When developing updates, package developers need to keep in mind that any content above the demarcation will not be
77+
updated on client systems. Therefore, any changes to the script that are necessary for the update (e.g. new variables)
78+
may need to be communicated to users separately, and it should be assumed that these variables may not be set.
79+
7280
If a script does not have any customizations or provides though customizations through a UI, you do not need to include
7381
a demarcation.

0 commit comments

Comments
 (0)