File: Frigcal/code/Frigcal--source/tools/combine-calendars/combine-calendars.py
r"""
========================================================================
Combine all calendar files in a folder into one new calendar file.
Part of Frigcal 4.1.0, quixotely.com/Frigcal, with same terms of use.
Overview:
If you have made multiple calendars in the app or are using more than
one ICS file from other sources, this script can be run to combine
the calendars into a single calendar and ICS file. All events in all
the source folder's calendars are added to a new single calendar file.
This makes it slightly simpler to add new events because the default
calendar for new-event adds is automatically set to the sole calendar
if there is just one. The default can also be set manually in Settings.
This script is primarily meant for use on Windows, macOS, and Linux
PCs. It may not be able to access calendar folders on Android,
subject to hosting apps' permissions, and it is not available in
Android installs of the Frigcal app (see online host in next section).
Usage:
1) Get this script's source-code file. Either download this
file from www.quixotely.com/Frigcal or locate it in the
"tools" subfolder of the app's install folder on PCs.
2) Get Python 3.X (3.12 or later recommended). On PCs, get
it from www.python.org/downloads or other. On Android, use
an app that runs Python scripts, such as Termux or PyDroid 3.
3) Run this script with one of the following Python command lines:
- On Windows, type this in Command Prompt or PowerShell:
py -3 combine-calendars.py
- On macOS and Linux, type this in Terminal:
python3 combine-calendars.py
As usual, you must either first use "cd" to change to the
folder containing this script, or include the script's
folder path before its name in the command. On Windows:
cd folder\containing\this\script
py -3 combine-calendars.py
py -3 folder\containing\this\script\combine-calendars.py
4) In the console where you're running the script's command line,
reply to prompts asking for your source calendars folder, and
the folder and name of the new combined calendar. Full example:
$ cd folder\containing\this\script
$ py -3 combine-calendars.py
Path to your source calendar folder? C:\Users\me\Calendar
Path to folder for your new combined calendar? combo-folder
Name of the new combined calendar (.ics optional)? combined
Done: 7 calendars combined in combo-folder/combined.ics.
Tip: you can verify results by comparing the number of
events in calendar-file stats for loads of the source and
target folders. Change folders in Settings to load each.
You can use "." for any asked folder path to refer to the folder
in which this script is run, and the source folder may be given
in the command line itself, in which case only the new folder
and calendar are asked (this allows tab completions for source):
$ python3 combine-calendars.py /Users/me/MY-STUFF/Calendar
Path to folder for your new combined calendar? .
Name of the new combined calendar (.ics optional)? combined
5) After running this script, your combined folder will have a
".ics" file with all combined events. Load it in a new run
of Frigcal by selecting its folder in the app's Settings screen.
This script does not change or delete source-folder calendars.
========================================================================
"""
import sys, os, glob
# import icalendar, pytz # not needed for text-parsing approach
#---------------
# Get run inputs
#---------------
if len(sys.argv) > 1:
source_folder = sys.argv[1]
else:
source_folder = input('Path to your source calendar folder? ')
target_folder = input('Path to folder for your new combined calendar? ')
target_file = input('Name of the new combined calendar (.ics optional)? ')
if not target_file.endswith('.ics'):
target_file += '.ics'
#----------------
# Validate inputs
#----------------
if not os.path.exists(source_folder):
print('Error - source folder does not exist: cancelling script')
sys.exit(1)
if not os.path.exists(target_folder):
try:
os.makedirs(target_folder)
except:
print('Error - cannot create target folder: cancelling script')
sys.exit(1)
icsfiles = glob.glob(os.path.join(source_folder, '*.ics'))
icsfiles = [f for f in icsfiles if not f.startswith('._')] # macos junk
if not icsfiles:
print('Error - no calendars in source folder: cancelling script')
sys.exit(1)
#------------------
# Combine calendars
#------------------
# This could load calendars with code borrowed from storage.py,
# but it's much easier to parse event text out of ICS text files,
# from start of first event up to the end-calendar terminator.
combotext = ''
numconverts = 0
event_header_line = 'BEGIN:VEVENT\n'
calendar_end_line = 'END:VCALENDAR\n'
for icsfile in icsfiles:
icspath = os.path.join(source_folder, icsfile)
fileobj = open(icspath, mode='r', encoding='utf8')
filetext = fileobj.read()
fileobj.close()
startevents = filetext.find(event_header_line) # first
if startevents == -1:
print(f'Skipping calendar with no events: {icsfile}')
continue
endevents = filetext.find(calendar_end_line)
if startevents == -1:
print(f'Skipping calendar with no ending line: {icsfile}')
continue
elif filetext[endevents:] != calendar_end_line:
print(f'Skipping calendar with text after ending: {icsfile}')
continue
eventstext = filetext[startevents : endevents]
combotext += eventstext
numconverts += 1
#-----------------------
# Save combined calendar
#-----------------------
# caveat: hardcoded FC version (2.0 is iCal)
fullcalendartext = \
f"""BEGIN:VCALENDAR
VERSION:2.0
PRODID:Frigcal 4.1.0
{combotext}END:VCALENDAR"""
try:
combopath = os.path.join(target_folder, target_file)
combofile = open(combopath, mode='w', encoding='utf8')
combofile.write(fullcalendartext)
combofile.close()
except:
print(f'Error - could not create new calendar: {combopath}')
else:
print(f'\nDone: {numconverts} calendars combined in {combopath}.\n')
print('Tip: you can verify results by comparing the number of\n'
'events in calendar-file stats for loads of the source and\n'
'target folders. Change folders in Settings to load each.')
# and compare source/target #events after folder changes and loads in app