Enhance timeshift modify #4
3 changed files with 114 additions and 13 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -132,3 +132,7 @@ dmypy.json
|
||||||
.pyre/
|
.pyre/
|
||||||
app/config/calendar.json
|
app/config/calendar.json
|
||||||
/app/cache/
|
/app/cache/
|
||||||
|
|
||||||
|
# Development directories
|
||||||
|
/app/config/
|
||||||
|
.vscode
|
||||||
|
|
27
README.md
27
README.md
|
@ -1,4 +1,5 @@
|
||||||
# ICS Fusion
|
# ICS Fusion
|
||||||
|
Forked from [https://github.com/jdejaegh/ics-fusion](https://github.com/jdejaegh/ics-fusion)
|
||||||
## Introduction
|
## Introduction
|
||||||
ICS Fusion is a tool to merge multiple ics feed into a single ics calendar. Filters and modifications may be applied on the incoming feeds. The resulting ics can be accessed via an HTTP endpoint.
|
ICS Fusion is a tool to merge multiple ics feed into a single ics calendar. Filters and modifications may be applied on the incoming feeds. The resulting ics can be accessed via an HTTP endpoint.
|
||||||
|
|
||||||
|
@ -30,6 +31,11 @@ The JSON configuration file should look like the following.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
|
{
|
||||||
|
"conf": true,
|
||||||
|
"extends": "str",
|
||||||
|
"extendFail": "str",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"url":"str",
|
"url":"str",
|
||||||
"name":"str",
|
"name":"str",
|
||||||
|
@ -59,15 +65,18 @@ The JSON configuration file should look like the following.
|
||||||
},
|
},
|
||||||
"name":{
|
"name":{
|
||||||
"addPrefix":"str",
|
"addPrefix":"str",
|
||||||
"addSuffix":"str"
|
"addSuffix":"str",
|
||||||
|
"redactAs":"str"
|
||||||
},
|
},
|
||||||
"description":{
|
"description":{
|
||||||
"addPrefix":"str",
|
"addPrefix":"str",
|
||||||
"addSuffix":"str"
|
"addSuffix":"str",
|
||||||
|
"redactAs":"str"
|
||||||
},
|
},
|
||||||
"location":{
|
"location":{
|
||||||
"addPrefix":"str",
|
"addPrefix":"str",
|
||||||
"addSuffix":"str"
|
"addSuffix":"str",
|
||||||
|
"redactAs":"str"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,11 +107,23 @@ Only the `url` and the `name` field are mandatory.
|
||||||
- `location`: modification to apply to the location of the events
|
- `location`: modification to apply to the location of the events
|
||||||
- `addPrefix`: string to add at the beginning of the field
|
- `addPrefix`: string to add at the beginning of the field
|
||||||
- `addSuffix`: string to add at the end of the field
|
- `addSuffix`: string to add at the end of the field
|
||||||
|
- `redactAs`: Replaces the content of the field with the specified string
|
||||||
|
|
||||||
If multiple calendars are specified in the configuration list, their events will be merged in the resulting ics feed.
|
If multiple calendars are specified in the configuration list, their events will be merged in the resulting ics feed.
|
||||||
|
The first dataset with {"conf": "true",} specifies options that are globally applied to all calenders in the conf. Omit this set to disable. Options
|
||||||
|
- `extends`: string specifying the name (excluding .json) of another config file to extend.
|
||||||
|
- `extendFail`: string speciying the action to take if an extend fails, either "fail" or "ignore". Default is "fail".
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
Once the config file is created, the corresponding HTTP endpoint is accessible. For example, if the file `app/config/my-calendar.json` contains the configuration, the HTTP endpoint will be `http://localhost:8088/my-calendar`.
|
Once the config file is created, the corresponding HTTP endpoint is accessible. For example, if the file `app/config/my-calendar.json` contains the configuration, the HTTP endpoint will be `http://localhost:8088/my-calendar`.
|
||||||
|
|
||||||
|
A config can extend another config file, to do this the extended config should contain begin with`{
|
||||||
|
"conf": true,
|
||||||
|
"extends": <name of calendar>,
|
||||||
|
"extendFail": "fail",
|
||||||
|
},`
|
||||||
|
For an extend to work calendars MUST share the same name between the configs
|
||||||
|
An extending config cannot remove data from a base calendar but can modify fields
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
Currently, the application only merges events of the ics feeds, the alarms and todos are not supported.
|
Currently, the application only merges events of the ics feeds, the alarms and todos are not supported.
|
||||||
|
|
|
@ -34,15 +34,18 @@ The JSON configuration file used in this module has the following structure
|
||||||
},
|
},
|
||||||
"name":{
|
"name":{
|
||||||
"addPrefix":"str",
|
"addPrefix":"str",
|
||||||
"addSuffix":"str"
|
"addSuffix":"str",
|
||||||
|
"redactAs":"str"
|
||||||
},
|
},
|
||||||
"description":{
|
"description":{
|
||||||
"addPrefix":"str",
|
"addPrefix":"str",
|
||||||
"addSuffix":"str"
|
"addSuffix":"str",
|
||||||
|
"redactAs":"str"
|
||||||
},
|
},
|
||||||
"location":{
|
"location":{
|
||||||
"addPrefix":"str",
|
"addPrefix":"str",
|
||||||
"addSuffix":"str"
|
"addSuffix":"str",
|
||||||
|
"redactAs":"str"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,6 +72,7 @@ Only the url and the name field are mandatory.
|
||||||
- location: modification to apply to the location of the events
|
- location: modification to apply to the location of the events
|
||||||
- addPrefix: string to add at the beginning of the field
|
- addPrefix: string to add at the beginning of the field
|
||||||
- addSuffix: string to add at the end of the field
|
- addSuffix: string to add at the end of the field
|
||||||
|
- redactAs: string to replace the field with
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
@ -192,9 +196,16 @@ def modify_time(cal: Calendar, modify: dict) -> Calendar:
|
||||||
hour = 0 if not ("hour" in shift) else shift["hour"]
|
hour = 0 if not ("hour" in shift) else shift["hour"]
|
||||||
minute = 0 if not ("minute" in shift) else shift["minute"]
|
minute = 0 if not ("minute" in shift) else shift["minute"]
|
||||||
|
|
||||||
|
shift_minutes = (year * 365 * 24 * 60) + (month * 30 * 24 * 60) + (day * 24 * 60) + (hour * 60) + minute
|
||||||
|
|
||||||
|
if shift_minutes > 0:
|
||||||
for event in cal.events:
|
for event in cal.events:
|
||||||
event.end = event.end.shift(years=year, months=month, days=day, hours=hour, minutes=minute)
|
event.end = event.end.shift(minutes=shift_minutes)
|
||||||
event.begin = event.begin.shift(years=year, months=month, days=day, hours=hour, minutes=minute)
|
event.begin = event.begin.shift(minutes=shift_minutes)
|
||||||
|
elif shift_minutes < 0:
|
||||||
|
for event in cal.events:
|
||||||
|
event.begin = event.begin.shift(minutes=shift_minutes)
|
||||||
|
event.end = event.end.shift(minutes=shift_minutes)
|
||||||
|
|
||||||
return cal
|
return cal
|
||||||
|
|
||||||
|
@ -251,6 +262,18 @@ def modify_text(cal: Calendar, modify: dict, field_name: str) -> Calendar:
|
||||||
event.location = event.location + change["addSuffix"] \
|
event.location = event.location + change["addSuffix"] \
|
||||||
if event.location is not None else change["addSuffix"]
|
if event.location is not None else change["addSuffix"]
|
||||||
|
|
||||||
|
if "redactAs" in change:
|
||||||
|
for event in cal.events:
|
||||||
|
|
||||||
|
if field_name == "name":
|
||||||
|
event.name = change["redactAs"]
|
||||||
|
|
||||||
|
elif field_name == "description":
|
||||||
|
event.description = change["redactAs"]
|
||||||
|
|
||||||
|
elif field_name == "location":
|
||||||
|
event.location = change["redactAs"]
|
||||||
|
|
||||||
return cal
|
return cal
|
||||||
|
|
||||||
|
|
||||||
|
@ -332,7 +355,29 @@ def process(path: str, from_cache: bool = True) -> Calendar:
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
for entry in config:
|
for entry in config:
|
||||||
|
if "conf" in entry:
|
||||||
|
if entry.get("extends", None) is not None:
|
||||||
|
try:
|
||||||
|
o = "app/config/" + sanitize_filename(entry["extends"]) + ".json"
|
||||||
|
print("Try to open " + o)
|
||||||
|
file = open(o, "r")
|
||||||
|
baseConfig = json.loads(file.read())
|
||||||
|
file.close()
|
||||||
|
extendingConfig = config
|
||||||
|
try:
|
||||||
|
config = merge_json(baseConfig, extendingConfig)
|
||||||
|
except:
|
||||||
|
config = extendingConfig
|
||||||
|
|
||||||
|
except:
|
||||||
|
if entry.get("extendFail", "fail") == "fail":
|
||||||
|
raise FileNotFoundError("The calendar is not cached")
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
for entry in config:
|
||||||
|
if "conf" not in entry:
|
||||||
cal = load_cal(entry)
|
cal = load_cal(entry)
|
||||||
|
|
||||||
if "filters" in entry:
|
if "filters" in entry:
|
||||||
|
@ -406,7 +451,7 @@ def load_cal(entry: dict) -> Calendar:
|
||||||
else:
|
else:
|
||||||
cal = Calendar(imports=r.content.decode())
|
cal = Calendar(imports=r.content.decode())
|
||||||
|
|
||||||
cal = horodate(cal, 'Downloaded at')
|
cal = horodate(cal, 'Event last fetched: ')
|
||||||
return cal
|
return cal
|
||||||
|
|
||||||
|
|
||||||
|
@ -432,3 +477,34 @@ def horodate(cal: Calendar, prefix='') -> Calendar:
|
||||||
if event.description is not None else prefix + ' ' + now
|
if event.description is not None else prefix + ' ' + now
|
||||||
|
|
||||||
return cal
|
return cal
|
||||||
|
|
||||||
|
def merge_json(base, extension):
|
||||||
|
"""Merges two config files by updating the value of base with the values in extension.
|
||||||
|
|
||||||
|
:param base: the base config file
|
||||||
|
:type base: dict
|
||||||
|
|
||||||
|
:param extension: the config file to merge with the base
|
||||||
|
:type extension: dict
|
||||||
|
|
||||||
|
:return: the merged config file
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
|
||||||
|
new_json = base.copy()
|
||||||
|
|
||||||
|
def update_json(base_set, updates):
|
||||||
|
for key, value in updates.items():
|
||||||
|
if not key == "conf":
|
||||||
|
if isinstance(value, dict) and key in base_set and isinstance(base_set[key], dict):
|
||||||
|
update_json(base_set[key], value)
|
||||||
|
else:
|
||||||
|
base_set[key] = value
|
||||||
|
|
||||||
|
for base_dataset in new_json:
|
||||||
|
if "conf" not in base_dataset:
|
||||||
|
for ext_dataset in extension:
|
||||||
|
if base_dataset.get("name") == ext_dataset.get("name"):
|
||||||
|
update_json(base_dataset, ext_dataset)
|
||||||
|
|
||||||
|
return new_json
|
||||||
|
|
Loading…
Add table
Reference in a new issue