125 lines
4.2 KiB
Python
125 lines
4.2 KiB
Python
#!/usr/bin/env python
|
|
import os
|
|
import argparse
|
|
import psycopg2
|
|
import yaml
|
|
from urllib.parse import urlparse
|
|
|
|
def get_config(config_path):
|
|
with open(config_path, 'r') as f:
|
|
return yaml.safe_load(f)
|
|
|
|
def count_events(data):
|
|
return data.count('BEGIN:VEVENT')
|
|
|
|
def import_events():
|
|
parser = argparse.ArgumentParser(description='Import CalDAV events from files to database.')
|
|
parser.add_argument('--config', default='config.yaml', help='Path to config.yaml')
|
|
parser.add_argument('--input', default='export', help='Input directory or file')
|
|
parser.add_argument('--user', help='Target user (filter or override)')
|
|
parser.add_argument('--calendar', help='Target calendar (filter or override)')
|
|
|
|
args = parser.parse_args()
|
|
|
|
cfg = get_config(args.config)
|
|
db_url = cfg.get('database', {}).get('url')
|
|
if not db_url:
|
|
print("Error: Could not find database URL in config.")
|
|
return
|
|
|
|
try:
|
|
conn = psycopg2.connect(db_url)
|
|
cur = conn.cursor()
|
|
except Exception as e:
|
|
print(f"Error connecting to database: {e}")
|
|
return
|
|
|
|
def upload_file(file_path, user_name, cal_name):
|
|
cur.execute("""
|
|
SELECT c.id, c.path
|
|
FROM calendars c
|
|
JOIN users u ON c.owner_id = u.id
|
|
WHERE u.name = %s AND c.name = %s
|
|
""", (user_name, cal_name))
|
|
res = cur.fetchone()
|
|
if not res:
|
|
print(f"Warning: Calendar '{cal_name}' for user '{user_name}' not found in DB. Skipping {file_path}")
|
|
return False
|
|
|
|
cal_id, cal_base_path = res
|
|
with open(file_path, 'r') as f:
|
|
data = f.read()
|
|
|
|
filename = os.path.basename(file_path)
|
|
obj_path = os.path.join(cal_base_path, filename)
|
|
etag = f'"{count_events(data)}"'
|
|
|
|
cur.execute("""
|
|
INSERT INTO calendar_objects (calendar_id, path, data, etag)
|
|
VALUES (%s, %s, %s, %s)
|
|
ON CONFLICT (calendar_id, path) DO UPDATE
|
|
SET data = EXCLUDED.data, etag = EXCLUDED.etag
|
|
""", (cal_id, obj_path, data, etag))
|
|
return True
|
|
|
|
# Gather all files
|
|
files_to_process = []
|
|
if os.path.isfile(args.input):
|
|
files_to_process.append(args.input)
|
|
elif os.path.isdir(args.input):
|
|
for root, _, filenames in os.walk(args.input):
|
|
for f in filenames:
|
|
if f.endswith('.ics'):
|
|
files_to_process.append(os.path.join(root, f))
|
|
else:
|
|
print(f"Error: Input '{args.input}' not found.")
|
|
return
|
|
|
|
success_count = 0
|
|
for f_path in files_to_process:
|
|
# Determine source structure
|
|
# If input is a dir, rel_path is relative to it. If file, it's just the filename.
|
|
if os.path.isdir(args.input):
|
|
rel_path = os.path.relpath(f_path, args.input)
|
|
else:
|
|
rel_path = os.path.basename(f_path)
|
|
|
|
parts = rel_path.split(os.sep)
|
|
|
|
source_user = None
|
|
source_cal = None
|
|
|
|
# Structure: user/calendar/file.ics (len 3)
|
|
if len(parts) >= 3:
|
|
source_user = parts[-3]
|
|
source_cal = parts[-2]
|
|
# Structure: calendar/file.ics (len 2)
|
|
elif len(parts) == 2:
|
|
source_cal = parts[-2]
|
|
|
|
# 1. Apply Filtering: If flag is set and we have a source value, they must match.
|
|
if args.user and source_user and args.user != source_user:
|
|
continue
|
|
if args.calendar and source_cal and args.calendar != source_cal:
|
|
continue
|
|
|
|
# 2. Determine Target: Flag overrides source.
|
|
target_user = args.user or source_user
|
|
target_cal = args.calendar or source_cal
|
|
|
|
if not target_user or not target_cal:
|
|
print(f"Skipping {f_path}: Cannot determine user/calendar. Use --user and --calendar flags.")
|
|
continue
|
|
|
|
if upload_file(f_path, target_user, target_cal):
|
|
print(f"Imported: {target_user}/{target_cal}/{os.path.basename(f_path)}")
|
|
success_count += 1
|
|
|
|
conn.commit()
|
|
cur.close()
|
|
conn.close()
|
|
print(f"Done. Successfully imported {success_count} events.")
|
|
|
|
if __name__ == "__main__":
|
|
import_events()
|