Add portrait display to timeline and tooltips, add portrait generation to demo family
- Add small portraits (24px) next to timeline events for person events - Add larger portraits (120px) in tooltips when hovering over events - Implement portrait loading from Gramps media_list using media_path_full - Add portrait drawing using Cairo with circular clipping and border - Update demo family generator to create portraits using DiceBear Avatars API - Generate portraits considering age and gender for appropriate appearance - Add media objects to XML with proper IDs and gallery references - Use relative paths for media files in demo family XML - Add helper scripts for debugging Gramps log files - Fix deprecation warnings for XML element truth value checks
This commit is contained in:
parent
cd58b85b42
commit
7119aedd3d
165
MyTimeline.py
165
MyTimeline.py
@ -40,6 +40,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
from gi.repository import Gdk
|
from gi.repository import Gdk
|
||||||
|
from gi.repository import GdkPixbuf
|
||||||
from gi.repository import GLib
|
from gi.repository import GLib
|
||||||
from gi.repository import Pango
|
from gi.repository import Pango
|
||||||
from gi.repository import PangoCairo
|
from gi.repository import PangoCairo
|
||||||
@ -130,6 +131,11 @@ TOOLTIP_LABEL_MARGIN = 5 # Margin for tooltip label
|
|||||||
TOOLTIP_MAX_WIDTH_CHARS = 40 # Maximum width in characters for tooltip label
|
TOOLTIP_MAX_WIDTH_CHARS = 40 # Maximum width in characters for tooltip label
|
||||||
TOOLTIP_BORDER_WIDTH = 8 # Border width for tooltip window
|
TOOLTIP_BORDER_WIDTH = 8 # Border width for tooltip window
|
||||||
|
|
||||||
|
# Portrait Display Constants
|
||||||
|
PORTRAIT_SIZE_TIMELINE = 24 # Size of timeline portraits
|
||||||
|
PORTRAIT_SIZE_TOOLTIP = 120 # Size of tooltip portraits
|
||||||
|
PORTRAIT_MARGIN = 5 # Margin around portraits
|
||||||
|
|
||||||
# Font Constants
|
# Font Constants
|
||||||
FONT_FAMILY = "Sans"
|
FONT_FAMILY = "Sans"
|
||||||
FONT_SIZE_NORMAL = 11
|
FONT_SIZE_NORMAL = 11
|
||||||
@ -2795,6 +2801,84 @@ class MyTimelineView(NavigationView):
|
|||||||
logger.debug(f"Error accessing place information for event in tooltip: {e}")
|
logger.debug(f"Error accessing place information for event in tooltip: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _get_person_portrait_path(self, person: Optional['Person']) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
Get the file path for a person's portrait from their media_list.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
person: The person object.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[str]: File path to portrait if available, None otherwise.
|
||||||
|
"""
|
||||||
|
if not person or not self.dbstate.is_open():
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
media_list = person.get_media_list()
|
||||||
|
if not media_list:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get the first media reference (primary portrait)
|
||||||
|
media_ref = media_list[0]
|
||||||
|
media_handle = media_ref.get_reference_handle()
|
||||||
|
|
||||||
|
# Get the media object from database
|
||||||
|
media_obj = self.dbstate.db.get_media_from_handle(media_handle)
|
||||||
|
if not media_obj:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get the file path
|
||||||
|
path = media_obj.get_path()
|
||||||
|
if not path:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Resolve relative paths using Gramps media path resolution
|
||||||
|
# Gramps stores paths relative to the database directory
|
||||||
|
# We need to resolve them to absolute paths
|
||||||
|
try:
|
||||||
|
from gramps.gen.utils.file import media_path_full
|
||||||
|
full_path = media_path_full(self.dbstate.db, path)
|
||||||
|
return full_path
|
||||||
|
except (ImportError, AttributeError):
|
||||||
|
# Fallback: try to use path as-is or resolve relative to current directory
|
||||||
|
import os
|
||||||
|
if os.path.isabs(path):
|
||||||
|
return path if os.path.exists(path) else None
|
||||||
|
# Try relative to current directory
|
||||||
|
if os.path.exists(path):
|
||||||
|
return os.path.abspath(path)
|
||||||
|
return None
|
||||||
|
|
||||||
|
except (AttributeError, KeyError, IndexError) as e:
|
||||||
|
logger.debug(f"Error accessing portrait for person: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _load_portrait_image(self, file_path: str, size: int) -> Optional[GdkPixbuf.Pixbuf]:
|
||||||
|
"""
|
||||||
|
Load and scale a portrait image.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: Path to the image file.
|
||||||
|
size: Target size (width and height) for the image.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[GdkPixbuf.Pixbuf]: Scaled pixbuf if successful, None otherwise.
|
||||||
|
"""
|
||||||
|
if not file_path:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Load the image
|
||||||
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(file_path)
|
||||||
|
|
||||||
|
# Scale to target size maintaining aspect ratio
|
||||||
|
scaled_pixbuf = pixbuf.scale_simple(size, size, GdkPixbuf.InterpType.BILINEAR)
|
||||||
|
return scaled_pixbuf
|
||||||
|
except (GLib.GError, Exception) as e:
|
||||||
|
logger.debug(f"Error loading portrait image from {file_path}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
def _format_person_tooltip(self, person: 'Person', person_events: List[TimelineEvent]) -> str:
|
def _format_person_tooltip(self, person: 'Person', person_events: List[TimelineEvent]) -> str:
|
||||||
"""
|
"""
|
||||||
Format tooltip for person with multiple events.
|
Format tooltip for person with multiple events.
|
||||||
@ -2917,16 +3001,32 @@ class MyTimelineView(NavigationView):
|
|||||||
frame.set_shadow_type(Gtk.ShadowType.OUT)
|
frame.set_shadow_type(Gtk.ShadowType.OUT)
|
||||||
frame.get_style_context().add_class("tooltip")
|
frame.get_style_context().add_class("tooltip")
|
||||||
|
|
||||||
|
# Create horizontal box for portrait and text
|
||||||
|
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=PORTRAIT_MARGIN)
|
||||||
|
hbox.set_margin_start(TOOLTIP_LABEL_MARGIN)
|
||||||
|
hbox.set_margin_end(TOOLTIP_LABEL_MARGIN)
|
||||||
|
hbox.set_margin_top(TOOLTIP_LABEL_MARGIN)
|
||||||
|
hbox.set_margin_bottom(TOOLTIP_LABEL_MARGIN)
|
||||||
|
|
||||||
|
# Add portrait if person has one
|
||||||
|
if event_data.person:
|
||||||
|
portrait_path = self._get_person_portrait_path(event_data.person)
|
||||||
|
if portrait_path:
|
||||||
|
pixbuf = self._load_portrait_image(portrait_path, PORTRAIT_SIZE_TOOLTIP)
|
||||||
|
if pixbuf:
|
||||||
|
portrait_image = Gtk.Image.new_from_pixbuf(pixbuf)
|
||||||
|
hbox.pack_start(portrait_image, False, False, 0)
|
||||||
|
|
||||||
|
# Add text label
|
||||||
label = Gtk.Label()
|
label = Gtk.Label()
|
||||||
label.set_markup(tooltip_text)
|
label.set_markup(tooltip_text)
|
||||||
label.set_line_wrap(True)
|
label.set_line_wrap(True)
|
||||||
label.set_max_width_chars(TOOLTIP_MAX_WIDTH_CHARS)
|
label.set_max_width_chars(TOOLTIP_MAX_WIDTH_CHARS)
|
||||||
label.set_margin_start(TOOLTIP_LABEL_MARGIN)
|
label.set_halign(Gtk.Align.START)
|
||||||
label.set_margin_end(TOOLTIP_LABEL_MARGIN)
|
label.set_valign(Gtk.Align.START)
|
||||||
label.set_margin_top(TOOLTIP_LABEL_MARGIN)
|
hbox.pack_start(label, True, True, 0)
|
||||||
label.set_margin_bottom(TOOLTIP_LABEL_MARGIN)
|
|
||||||
|
|
||||||
frame.add(label)
|
frame.add(hbox)
|
||||||
tooltip_window.add(frame)
|
tooltip_window.add(frame)
|
||||||
tooltip_window.show_all()
|
tooltip_window.show_all()
|
||||||
|
|
||||||
@ -3017,8 +3117,17 @@ class MyTimelineView(NavigationView):
|
|||||||
self.draw_event_marker(context, timeline_x, event_data.y_pos,
|
self.draw_event_marker(context, timeline_x, event_data.y_pos,
|
||||||
event_data.event_type, is_hovered, is_selected)
|
event_data.event_type, is_hovered, is_selected)
|
||||||
|
|
||||||
|
# Draw portrait if person has one (between timeline and label)
|
||||||
|
if event_data.person:
|
||||||
|
portrait_x = timeline_x + PORTRAIT_MARGIN + PORTRAIT_SIZE_TIMELINE / 2
|
||||||
|
self.draw_portrait(context, portrait_x, event_data.y_pos,
|
||||||
|
event_data.person, PORTRAIT_SIZE_TIMELINE)
|
||||||
|
|
||||||
# Draw event label
|
# Draw event label
|
||||||
label_x = timeline_x + LABEL_X_OFFSET
|
label_x = timeline_x + LABEL_X_OFFSET
|
||||||
|
# Adjust label position if portrait is present
|
||||||
|
if event_data.person:
|
||||||
|
label_x = timeline_x + PORTRAIT_SIZE_TIMELINE + PORTRAIT_MARGIN * 2 + LABEL_X_OFFSET
|
||||||
self.draw_event_label(
|
self.draw_event_label(
|
||||||
context, label_x, event_data.y_pos, event_data.date_obj,
|
context, label_x, event_data.y_pos, event_data.date_obj,
|
||||||
event_data.event, event_data.person, event_data.event_type, is_hovered
|
event_data.event, event_data.person, event_data.event_type, is_hovered
|
||||||
@ -3317,6 +3426,52 @@ class MyTimelineView(NavigationView):
|
|||||||
|
|
||||||
context.restore()
|
context.restore()
|
||||||
|
|
||||||
|
def draw_portrait(self, context: cairo.Context, x: float, y: float,
|
||||||
|
person: Optional['Person'], size: int = PORTRAIT_SIZE_TIMELINE) -> None:
|
||||||
|
"""
|
||||||
|
Draw a circular portrait for a person.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
context: Cairo drawing context.
|
||||||
|
x: X coordinate for portrait center.
|
||||||
|
y: Y coordinate for portrait center.
|
||||||
|
person: The person object (None if no person or no portrait).
|
||||||
|
size: Size of the portrait (diameter).
|
||||||
|
"""
|
||||||
|
if not person:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get portrait path and load image
|
||||||
|
portrait_path = self._get_person_portrait_path(person)
|
||||||
|
if not portrait_path:
|
||||||
|
return
|
||||||
|
|
||||||
|
pixbuf = self._load_portrait_image(portrait_path, size)
|
||||||
|
if not pixbuf:
|
||||||
|
return
|
||||||
|
|
||||||
|
context.save()
|
||||||
|
|
||||||
|
# Create a circular clipping path
|
||||||
|
radius = size / 2
|
||||||
|
context.arc(x, y, radius, 0, 2 * math.pi)
|
||||||
|
context.clip()
|
||||||
|
|
||||||
|
# Use Gdk to set the pixbuf as source for Cairo
|
||||||
|
# This is the proper way to draw a GdkPixbuf with Cairo
|
||||||
|
Gdk.cairo_set_source_pixbuf(context, pixbuf, x - radius, y - radius)
|
||||||
|
context.paint()
|
||||||
|
|
||||||
|
context.restore()
|
||||||
|
|
||||||
|
# Draw a border around the portrait
|
||||||
|
context.save()
|
||||||
|
context.set_source_rgba(0.0, 0.0, 0.0, 0.3) # Semi-transparent black border
|
||||||
|
context.set_line_width(1.5)
|
||||||
|
context.arc(x, y, radius, 0, 2 * math.pi)
|
||||||
|
context.stroke()
|
||||||
|
context.restore()
|
||||||
|
|
||||||
def _find_year_range(self) -> Tuple[Optional[int], Optional[int]]:
|
def _find_year_range(self) -> Tuple[Optional[int], Optional[int]]:
|
||||||
"""
|
"""
|
||||||
Find the minimum and maximum years from all events.
|
Find the minimum and maximum years from all events.
|
||||||
|
|||||||
89
README_LOGGING.md
Normal file
89
README_LOGGING.md
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
# Gramps Logging and Media Import Debugging
|
||||||
|
|
||||||
|
This directory contains scripts to help debug media import issues in Gramps.
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
### 1. `check_gramps_logs.sh`
|
||||||
|
Checks for existing Gramps log files and displays media-related errors and messages.
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```bash
|
||||||
|
./check_gramps_logs.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This script will:
|
||||||
|
- Check for log files in the snap directory
|
||||||
|
- Display media-related errors and warnings
|
||||||
|
- Show recent log entries
|
||||||
|
- Check alternative log file locations
|
||||||
|
|
||||||
|
### 2. `run_gramps_with_logging.sh`
|
||||||
|
Runs Gramps with debug logging enabled and monitors the log file in real-time.
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```bash
|
||||||
|
./run_gramps_with_logging.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This script will:
|
||||||
|
- Start Gramps with debug logging for media and import operations
|
||||||
|
- Monitor the log file as it's created
|
||||||
|
- Display log entries in real-time
|
||||||
|
|
||||||
|
## How to Debug Media Import Issues
|
||||||
|
|
||||||
|
1. **Run Gramps with logging:**
|
||||||
|
```bash
|
||||||
|
./run_gramps_with_logging.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **In Gramps:**
|
||||||
|
- Import the `demo_family.gramps` file
|
||||||
|
- Note any error messages in the Gramps UI
|
||||||
|
- Close Gramps when done
|
||||||
|
|
||||||
|
3. **Check the logs:**
|
||||||
|
```bash
|
||||||
|
./check_gramps_logs.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Look for:**
|
||||||
|
- Media file path errors
|
||||||
|
- Import errors
|
||||||
|
- File not found messages
|
||||||
|
- Permission errors
|
||||||
|
|
||||||
|
## Manual Log File Location
|
||||||
|
|
||||||
|
For Gramps installed via snap, log files are located at:
|
||||||
|
```
|
||||||
|
~/snap/gramps/11/.config/gramps/gramps60/logs/Gramps60.log
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
### Media files not found
|
||||||
|
- Check that the `portraits/` directory is in the same directory as `demo_family.gramps`
|
||||||
|
- Verify file paths in the XML are relative (e.g., `portraits/portrait_0001_John_Smith.svg`)
|
||||||
|
- Set the base media path in Gramps: Edit → Preferences → Family Tree → Base media path
|
||||||
|
|
||||||
|
### Import errors
|
||||||
|
- Check the log file for specific error messages
|
||||||
|
- Verify XML structure is valid
|
||||||
|
- Ensure all media objects have proper `id` attributes
|
||||||
|
|
||||||
|
## Enabling Logging Manually
|
||||||
|
|
||||||
|
If you prefer to run Gramps manually with logging:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gramps --debug=gramps.gen.lib.media \
|
||||||
|
--debug=gramps.plugins.import.importxml \
|
||||||
|
--debug=gramps.gen.db
|
||||||
|
```
|
||||||
|
|
||||||
|
Or enable all logging:
|
||||||
|
```bash
|
||||||
|
gramps --debug=all
|
||||||
|
```
|
||||||
121
check_gramps_logs.sh
Executable file
121
check_gramps_logs.sh
Executable file
@ -0,0 +1,121 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Script to check Gramps logs for media import errors
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
echo "=== Gramps Log File Checker ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Determine log directory
|
||||||
|
LOG_DIR="$HOME/snap/gramps/11/.config/gramps/gramps60/logs"
|
||||||
|
LOG_FILE="$LOG_DIR/Gramps60.log"
|
||||||
|
|
||||||
|
echo "Checking for log directory: $LOG_DIR"
|
||||||
|
|
||||||
|
# Create logs directory if it doesn't exist
|
||||||
|
if [ ! -d "$LOG_DIR" ]; then
|
||||||
|
echo -e "${YELLOW}Logs directory doesn't exist. Creating it...${NC}"
|
||||||
|
mkdir -p "$LOG_DIR"
|
||||||
|
echo -e "${GREEN}Created: $LOG_DIR${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${GREEN}Logs directory exists: $LOG_DIR${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Checking for log file: $LOG_FILE"
|
||||||
|
|
||||||
|
# Check if log file exists
|
||||||
|
if [ -f "$LOG_FILE" ]; then
|
||||||
|
echo -e "${GREEN}Log file found!${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "=== Log File Info ==="
|
||||||
|
ls -lh "$LOG_FILE"
|
||||||
|
echo ""
|
||||||
|
echo "Last modified: $(stat -c %y "$LOG_FILE" 2>/dev/null || stat -f %Sm "$LOG_FILE" 2>/dev/null || echo "Unknown")"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Count lines
|
||||||
|
LINE_COUNT=$(wc -l < "$LOG_FILE")
|
||||||
|
echo "Total lines in log: $LINE_COUNT"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Search for media-related errors
|
||||||
|
echo "=== Searching for Media-Related Messages ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check for errors
|
||||||
|
ERROR_COUNT=$(grep -i "error\|exception\|traceback\|failed" "$LOG_FILE" | wc -l)
|
||||||
|
if [ "$ERROR_COUNT" -gt 0 ]; then
|
||||||
|
echo -e "${RED}Found $ERROR_COUNT error/exception entries${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Recent errors:"
|
||||||
|
grep -i "error\|exception\|traceback\|failed" "$LOG_FILE" | tail -20
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo -e "${GREEN}No errors found in log file${NC}"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for media-related entries
|
||||||
|
MEDIA_COUNT=$(grep -i "media\|portrait\|file\|import" "$LOG_FILE" | wc -l)
|
||||||
|
if [ "$MEDIA_COUNT" -gt 0 ]; then
|
||||||
|
echo -e "${YELLOW}Found $MEDIA_COUNT media-related entries${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Recent media-related messages:"
|
||||||
|
grep -i "media\|portrait\|file\|import" "$LOG_FILE" | tail -30
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}No media-related entries found${NC}"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show last 50 lines
|
||||||
|
echo "=== Last 50 Lines of Log ==="
|
||||||
|
tail -50 "$LOG_FILE"
|
||||||
|
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}Log file not found yet.${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "To create the log file, run Gramps with debug logging enabled:"
|
||||||
|
echo ""
|
||||||
|
echo " gramps --debug=all"
|
||||||
|
echo ""
|
||||||
|
echo "Or specifically for media import:"
|
||||||
|
echo ""
|
||||||
|
echo " gramps --debug=gramps.gen.lib.media --debug=gramps.plugins.import.importxml"
|
||||||
|
echo ""
|
||||||
|
echo "After running Gramps and attempting to import, run this script again to check for errors."
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Also check for other possible log locations
|
||||||
|
echo ""
|
||||||
|
echo "=== Checking Alternative Log Locations ==="
|
||||||
|
ALTERNATIVE_LOCATIONS=(
|
||||||
|
"$HOME/.gramps/Gramps60.log"
|
||||||
|
"$HOME/.gramps/Gramps51.log"
|
||||||
|
"$HOME/.gramps/logs/Gramps60.log"
|
||||||
|
"$HOME/snap/gramps/common/.gramps/Gramps60.log"
|
||||||
|
"./gramps_debug.log"
|
||||||
|
"$HOME/gramps_debug.log"
|
||||||
|
)
|
||||||
|
|
||||||
|
for loc in "${ALTERNATIVE_LOCATIONS[@]}"; do
|
||||||
|
if [ -f "$loc" ]; then
|
||||||
|
echo -e "${GREEN}Found log file: $loc${NC}"
|
||||||
|
ls -lh "$loc"
|
||||||
|
echo ""
|
||||||
|
echo "Media-related entries in $loc:"
|
||||||
|
grep -i "media\|portrait\|file\|import\|error" "$loc" | tail -20
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Script Complete ==="
|
||||||
@ -5,6 +5,10 @@ Generate a huge demo family for Gramps testing
|
|||||||
|
|
||||||
import random
|
import random
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
import os
|
||||||
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
|
import urllib.parse
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional, List, Tuple, Dict
|
from typing import Optional, List, Tuple, Dict
|
||||||
@ -24,6 +28,10 @@ GRAMPS_XML_VERSION = "5.1.0"
|
|||||||
GRAMPS_XML_NAMESPACE = "http://gramps-project.org/xml/1.7.1/"
|
GRAMPS_XML_NAMESPACE = "http://gramps-project.org/xml/1.7.1/"
|
||||||
GRAMPS_XML_DTD = "http://gramps-project.org/xml/1.7.1/grampsxml.dtd"
|
GRAMPS_XML_DTD = "http://gramps-project.org/xml/1.7.1/grampsxml.dtd"
|
||||||
|
|
||||||
|
# Portrait generation constants
|
||||||
|
PORTRAITS_DIR = "portraits"
|
||||||
|
DICEBEAR_API_BASE = "https://api.dicebear.com/7.x/avataaars/svg"
|
||||||
|
|
||||||
# Event types to add
|
# Event types to add
|
||||||
EVENT_TYPES = [
|
EVENT_TYPES = [
|
||||||
("Baptism", 0.7, 0, 2), # 70% chance, 0-2 years after birth
|
("Baptism", 0.7, 0, 2), # 70% chance, 0-2 years after birth
|
||||||
@ -119,6 +127,118 @@ def gen_handle(prefix: str, num: int) -> str:
|
|||||||
return f"_{prefix}{num:08d}"
|
return f"_{prefix}{num:08d}"
|
||||||
|
|
||||||
|
|
||||||
|
def generate_portrait(person_id: int, name: str, gender: str, birth_year: int) -> Optional[Tuple[str, str]]:
|
||||||
|
"""
|
||||||
|
Generate a portrait for a person using DiceBear Avatars API.
|
||||||
|
Considers age and gender for appropriate portrait selection.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
person_id: Unique person ID.
|
||||||
|
name: Person's name (used as seed for deterministic generation).
|
||||||
|
gender: Person's gender ('M' or 'F').
|
||||||
|
birth_year: Birth year (used to determine age-appropriate features).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[Tuple[str, str]]: Tuple of (media_handle, file_path) if successful, None otherwise.
|
||||||
|
"""
|
||||||
|
# Create portraits directory if it doesn't exist
|
||||||
|
if not os.path.exists(PORTRAITS_DIR):
|
||||||
|
os.makedirs(PORTRAITS_DIR)
|
||||||
|
|
||||||
|
# Calculate approximate age (use current year or a fixed reference year)
|
||||||
|
current_year = datetime.now().year
|
||||||
|
age = current_year - birth_year
|
||||||
|
|
||||||
|
# Create seed from name, person_id, and gender for deterministic generation
|
||||||
|
# Include gender in seed to get different avatars for different genders
|
||||||
|
seed = f"{name}_{person_id}_{gender}"
|
||||||
|
|
||||||
|
# Build API URL with parameters
|
||||||
|
# DiceBear Avataaars doesn't have a gender parameter, but we can use seed
|
||||||
|
# to get deterministic results. The seed with gender included will produce
|
||||||
|
# different avatars for different genders.
|
||||||
|
params = {
|
||||||
|
"seed": seed
|
||||||
|
}
|
||||||
|
|
||||||
|
# Note: DiceBear Avataaars style doesn't support style parameter
|
||||||
|
# We rely on the seed for deterministic generation based on name, ID, and gender
|
||||||
|
|
||||||
|
# Build URL with proper encoding
|
||||||
|
url = f"{DICEBEAR_API_BASE}?{urllib.parse.urlencode(params)}"
|
||||||
|
|
||||||
|
# Generate filename
|
||||||
|
filename = f"portrait_{person_id:04d}_{name.replace(' ', '_')}.svg"
|
||||||
|
file_path = os.path.join(PORTRAITS_DIR, filename)
|
||||||
|
|
||||||
|
# Download portrait
|
||||||
|
try:
|
||||||
|
urllib.request.urlretrieve(url, file_path)
|
||||||
|
media_handle = gen_handle("MEDIA", person_id)
|
||||||
|
return (media_handle, file_path)
|
||||||
|
except (urllib.error.URLError, urllib.error.HTTPError, OSError) as e:
|
||||||
|
print(f"Warning: Could not generate portrait for {name}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def create_media_element(media_handle: str, file_path: str, title: str, media_id: Optional[int] = None) -> ET.Element:
|
||||||
|
"""
|
||||||
|
Create an XML element for a media object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
media_handle: Unique handle for the media object.
|
||||||
|
file_path: Path to the media file (relative to XML file location).
|
||||||
|
title: Title/description for the media object.
|
||||||
|
media_id: Optional media ID number. If None, extracted from handle.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ET.Element: The media XML element.
|
||||||
|
"""
|
||||||
|
media_elem = ET.Element("media")
|
||||||
|
media_elem.set("handle", media_handle)
|
||||||
|
media_elem.set("change", str(int(datetime.now().timestamp())))
|
||||||
|
|
||||||
|
# Extract ID from handle if not provided
|
||||||
|
# Handle format: _MEDIA00000001 -> ID: O0001
|
||||||
|
if media_id is None:
|
||||||
|
# Extract number from handle (e.g., "_MEDIA00000001" -> 1)
|
||||||
|
try:
|
||||||
|
media_id = int(media_handle.replace("_MEDIA", "").lstrip("0") or "0")
|
||||||
|
except (ValueError, AttributeError):
|
||||||
|
media_id = 0
|
||||||
|
|
||||||
|
media_elem.set("id", f"O{media_id:04d}")
|
||||||
|
|
||||||
|
# Use relative path for file (relative to XML file location)
|
||||||
|
# The file_path from generate_portrait is already relative (portraits/filename)
|
||||||
|
# If it's absolute, convert it to relative based on current working directory
|
||||||
|
if os.path.isabs(file_path):
|
||||||
|
# Get current working directory (where XML file will be saved)
|
||||||
|
cwd = os.getcwd()
|
||||||
|
try:
|
||||||
|
rel_path = os.path.relpath(file_path, cwd)
|
||||||
|
except ValueError:
|
||||||
|
# If paths are on different drives (Windows), keep absolute
|
||||||
|
rel_path = file_path
|
||||||
|
else:
|
||||||
|
# Already relative, use as-is
|
||||||
|
rel_path = file_path
|
||||||
|
|
||||||
|
# Normalize path separators (use forward slashes for cross-platform compatibility)
|
||||||
|
rel_path = rel_path.replace("\\", "/")
|
||||||
|
|
||||||
|
file_elem = ET.SubElement(media_elem, "file")
|
||||||
|
file_elem.set("src", rel_path)
|
||||||
|
|
||||||
|
title_elem = ET.SubElement(media_elem, "title")
|
||||||
|
title_elem.text = title
|
||||||
|
|
||||||
|
mime_elem = ET.SubElement(media_elem, "mimetype")
|
||||||
|
mime_elem.text = "image/svg+xml"
|
||||||
|
|
||||||
|
return media_elem
|
||||||
|
|
||||||
|
|
||||||
def create_event_element(event_data: EventData) -> ET.Element:
|
def create_event_element(event_data: EventData) -> ET.Element:
|
||||||
"""Create an XML element for an event."""
|
"""Create an XML element for an event."""
|
||||||
event_elem = ET.Element("event")
|
event_elem = ET.Element("event")
|
||||||
@ -220,8 +340,8 @@ def gen_person(
|
|||||||
parentin_families: Optional[List[str]] = None,
|
parentin_families: Optional[List[str]] = None,
|
||||||
childof_families: Optional[List[str]] = None,
|
childof_families: Optional[List[str]] = None,
|
||||||
reuse_additional_events: Optional[List[Tuple[str, EventData]]] = None
|
reuse_additional_events: Optional[List[Tuple[str, EventData]]] = None
|
||||||
) -> Tuple[ET.Element, ET.Element, Optional[ET.Element], List[ET.Element], List[Tuple[str, EventData]]]:
|
) -> Tuple[ET.Element, ET.Element, Optional[ET.Element], List[ET.Element], List[Tuple[str, EventData]], Optional[Tuple[str, str]]]:
|
||||||
"""Generate a person with all associated events."""
|
"""Generate a person with all associated events and portrait."""
|
||||||
handle = gen_handle("PERSON", pid)
|
handle = gen_handle("PERSON", pid)
|
||||||
birth_handle = gen_handle("EVENT", pid * EVENT_ID_OFFSET)
|
birth_handle = gen_handle("EVENT", pid * EVENT_ID_OFFSET)
|
||||||
death_handle = gen_handle("EVENT", pid * EVENT_ID_OFFSET + 1) if death_year else None
|
death_handle = gen_handle("EVENT", pid * EVENT_ID_OFFSET + 1) if death_year else None
|
||||||
@ -242,6 +362,16 @@ def gen_person(
|
|||||||
surname_elem = ET.SubElement(name_elem, "surname")
|
surname_elem = ET.SubElement(name_elem, "surname")
|
||||||
surname_elem.text = surname
|
surname_elem.text = surname
|
||||||
|
|
||||||
|
# Generate portrait and add gallery reference
|
||||||
|
full_name = f"{first_name} {surname}"
|
||||||
|
portrait_info = generate_portrait(pid, full_name, gender, birth_year)
|
||||||
|
if portrait_info:
|
||||||
|
media_handle, file_path = portrait_info
|
||||||
|
# Add gallery section with media reference
|
||||||
|
gallery_elem = ET.SubElement(person_elem, "gallery")
|
||||||
|
media_ref = ET.SubElement(gallery_elem, "mediaobjref")
|
||||||
|
media_ref.set("hlink", media_handle)
|
||||||
|
|
||||||
# Birth event reference
|
# Birth event reference
|
||||||
birth_ref = ET.SubElement(person_elem, "eventref")
|
birth_ref = ET.SubElement(person_elem, "eventref")
|
||||||
birth_ref.set("hlink", birth_handle)
|
birth_ref.set("hlink", birth_handle)
|
||||||
@ -309,7 +439,7 @@ def gen_person(
|
|||||||
# Convert additional events to XML elements
|
# Convert additional events to XML elements
|
||||||
all_additional_events_xml = [create_event_element(event_data) for _, event_data in additional_events]
|
all_additional_events_xml = [create_event_element(event_data) for _, event_data in additional_events]
|
||||||
|
|
||||||
return person_elem, birth_event, death_event, all_additional_events_xml, additional_events
|
return person_elem, birth_event, death_event, all_additional_events_xml, additional_events, portrait_info
|
||||||
|
|
||||||
|
|
||||||
def gen_family(
|
def gen_family(
|
||||||
@ -366,7 +496,8 @@ def gen_family(
|
|||||||
def create_gramps_xml_document(
|
def create_gramps_xml_document(
|
||||||
events: List[ET.Element],
|
events: List[ET.Element],
|
||||||
people: List[ET.Element],
|
people: List[ET.Element],
|
||||||
families: List[ET.Element]
|
families: List[ET.Element],
|
||||||
|
media: List[ET.Element]
|
||||||
) -> ET.ElementTree:
|
) -> ET.ElementTree:
|
||||||
"""Create the complete Gramps XML document."""
|
"""Create the complete Gramps XML document."""
|
||||||
# Create root element
|
# Create root element
|
||||||
@ -401,6 +532,11 @@ def create_gramps_xml_document(
|
|||||||
for family in families:
|
for family in families:
|
||||||
families_elem.append(family)
|
families_elem.append(family)
|
||||||
|
|
||||||
|
# Media objects
|
||||||
|
media_elem = ET.SubElement(database, "objects")
|
||||||
|
for media_obj in media:
|
||||||
|
media_elem.append(media_obj)
|
||||||
|
|
||||||
return ET.ElementTree(database)
|
return ET.ElementTree(database)
|
||||||
|
|
||||||
|
|
||||||
@ -413,7 +549,7 @@ def main() -> None:
|
|||||||
father_id = 1
|
father_id = 1
|
||||||
father_handle = gen_handle("PERSON", father_id)
|
father_handle = gen_handle("PERSON", father_id)
|
||||||
main_family_handle = gen_handle("FAMILY", 1)
|
main_family_handle = gen_handle("FAMILY", 1)
|
||||||
father_person, father_birth, father_death, father_additional_xml, _ = gen_person(
|
father_person, father_birth, father_death, father_additional_xml, _, father_portrait = gen_person(
|
||||||
father_id, "John", "Smith", 1950, 2010, "M",
|
father_id, "John", "Smith", 1950, 2010, "M",
|
||||||
parentin_families=[main_family_handle]
|
parentin_families=[main_family_handle]
|
||||||
)
|
)
|
||||||
@ -421,16 +557,27 @@ def main() -> None:
|
|||||||
# Mother: Mary Smith, born 1952, died 2015
|
# Mother: Mary Smith, born 1952, died 2015
|
||||||
mother_id = 2
|
mother_id = 2
|
||||||
mother_handle = gen_handle("PERSON", mother_id)
|
mother_handle = gen_handle("PERSON", mother_id)
|
||||||
mother_person, mother_birth, mother_death, mother_additional_xml, _ = gen_person(
|
mother_person, mother_birth, mother_death, mother_additional_xml, _, mother_portrait = gen_person(
|
||||||
mother_id, "Mary", "Smith", 1952, 2015, "F",
|
mother_id, "Mary", "Smith", 1952, 2015, "F",
|
||||||
parentin_families=[main_family_handle]
|
parentin_families=[main_family_handle]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Collect media elements
|
||||||
|
all_media: List[ET.Element] = []
|
||||||
|
if father_portrait:
|
||||||
|
media_handle, file_path = father_portrait
|
||||||
|
media_elem = create_media_element(media_handle, file_path, "Portrait of John Smith", father_id)
|
||||||
|
all_media.append(media_elem)
|
||||||
|
if mother_portrait:
|
||||||
|
media_handle, file_path = mother_portrait
|
||||||
|
media_elem = create_media_element(media_handle, file_path, "Portrait of Mary Smith", mother_id)
|
||||||
|
all_media.append(media_elem)
|
||||||
|
|
||||||
all_additional_events = father_additional_xml + mother_additional_xml
|
all_additional_events = father_additional_xml + mother_additional_xml
|
||||||
all_events = [father_birth, mother_birth]
|
all_events = [father_birth, mother_birth]
|
||||||
if father_death:
|
if father_death is not None:
|
||||||
all_events.append(father_death)
|
all_events.append(father_death)
|
||||||
if mother_death:
|
if mother_death is not None:
|
||||||
all_events.append(mother_death)
|
all_events.append(mother_death)
|
||||||
|
|
||||||
# Generate 15 children
|
# Generate 15 children
|
||||||
@ -446,7 +593,7 @@ def main() -> None:
|
|||||||
death_year = birth_year + random.randint(60, 90) if random.random() < 0.3 else None # 30% chance of death
|
death_year = birth_year + random.randint(60, 90) if random.random() < 0.3 else None # 30% chance of death
|
||||||
|
|
||||||
child_handle = gen_handle("PERSON", child_id)
|
child_handle = gen_handle("PERSON", child_id)
|
||||||
child_person, child_birth, child_death, child_additional_xml, child_additional_tuples = gen_person(
|
child_person, child_birth, child_death, child_additional_xml, child_additional_tuples, child_portrait = gen_person(
|
||||||
child_id, first_name, "Smith", birth_year, death_year, gender,
|
child_id, first_name, "Smith", birth_year, death_year, gender,
|
||||||
childof_families=[main_family_handle]
|
childof_families=[main_family_handle]
|
||||||
)
|
)
|
||||||
@ -454,11 +601,16 @@ def main() -> None:
|
|||||||
children.append(child_person)
|
children.append(child_person)
|
||||||
child_handles.append(child_handle)
|
child_handles.append(child_handle)
|
||||||
all_events.append(child_birth)
|
all_events.append(child_birth)
|
||||||
if child_death:
|
if child_death is not None:
|
||||||
all_events.append(child_death)
|
all_events.append(child_death)
|
||||||
# Store tuples for reuse when regenerating
|
# Store tuples for reuse when regenerating
|
||||||
child_additional_events_map[child_id] = child_additional_tuples
|
child_additional_events_map[child_id] = child_additional_tuples
|
||||||
all_additional_events.extend(child_additional_xml)
|
all_additional_events.extend(child_additional_xml)
|
||||||
|
# Add portrait media if available
|
||||||
|
if child_portrait:
|
||||||
|
media_handle, file_path = child_portrait
|
||||||
|
media_elem = create_media_element(media_handle, file_path, f"Portrait of {first_name} Smith", child_id)
|
||||||
|
all_media.append(media_elem)
|
||||||
child_id += 1
|
child_id += 1
|
||||||
|
|
||||||
# Generate family
|
# Generate family
|
||||||
@ -548,15 +700,19 @@ def main() -> None:
|
|||||||
childof=[]
|
childof=[]
|
||||||
)
|
)
|
||||||
|
|
||||||
spouse_person, spouse_birth_event, spouse_death_event, spouse_additional_xml, _ = gen_person(
|
spouse_person, spouse_birth_event, spouse_death_event, spouse_additional_xml, _, spouse_portrait = gen_person(
|
||||||
grandchild_id, spouse_name, "Smith", spouse_birth, None, spouse_gender,
|
grandchild_id, spouse_name, "Smith", spouse_birth, None, spouse_gender,
|
||||||
parentin_families=[child_family_handle]
|
parentin_families=[child_family_handle]
|
||||||
)
|
)
|
||||||
grandchildren.append(spouse_person)
|
grandchildren.append(spouse_person)
|
||||||
all_events.append(spouse_birth_event)
|
all_events.append(spouse_birth_event)
|
||||||
if spouse_death_event:
|
if spouse_death_event is not None:
|
||||||
all_events.append(spouse_death_event)
|
all_events.append(spouse_death_event)
|
||||||
all_additional_events.extend(spouse_additional_xml)
|
all_additional_events.extend(spouse_additional_xml)
|
||||||
|
if spouse_portrait:
|
||||||
|
media_handle, file_path = spouse_portrait
|
||||||
|
media_elem = create_media_element(media_handle, file_path, f"Portrait of {spouse_name} Smith", grandchild_id)
|
||||||
|
all_media.append(media_elem)
|
||||||
grandchild_id += 1
|
grandchild_id += 1
|
||||||
|
|
||||||
# Update parent to include parentin reference
|
# Update parent to include parentin reference
|
||||||
@ -582,16 +738,20 @@ def main() -> None:
|
|||||||
childof=[child_family_handle]
|
childof=[child_family_handle]
|
||||||
)
|
)
|
||||||
|
|
||||||
gchild_person, gchild_birth_event, gchild_death_event, gchild_additional_xml, _ = gen_person(
|
gchild_person, gchild_birth_event, gchild_death_event, gchild_additional_xml, _, gchild_portrait = gen_person(
|
||||||
grandchild_id, gchild_name, "Smith", gchild_birth, None, gchild_gender,
|
grandchild_id, gchild_name, "Smith", gchild_birth, None, gchild_gender,
|
||||||
childof_families=[child_family_handle]
|
childof_families=[child_family_handle]
|
||||||
)
|
)
|
||||||
grandchildren.append(gchild_person)
|
grandchildren.append(gchild_person)
|
||||||
grandchild_handles.append(gchild_handle)
|
grandchild_handles.append(gchild_handle)
|
||||||
all_events.append(gchild_birth_event)
|
all_events.append(gchild_birth_event)
|
||||||
if gchild_death_event:
|
if gchild_death_event is not None:
|
||||||
all_events.append(gchild_death_event)
|
all_events.append(gchild_death_event)
|
||||||
all_additional_events.extend(gchild_additional_xml)
|
all_additional_events.extend(gchild_additional_xml)
|
||||||
|
if gchild_portrait:
|
||||||
|
media_handle, file_path = gchild_portrait
|
||||||
|
media_elem = create_media_element(media_handle, file_path, f"Portrait of {gchild_name} Smith", grandchild_id)
|
||||||
|
all_media.append(media_elem)
|
||||||
grandchild_id += 1
|
grandchild_id += 1
|
||||||
|
|
||||||
# Create family for this couple
|
# Create family for this couple
|
||||||
@ -608,7 +768,7 @@ def main() -> None:
|
|||||||
data = person_data[child_pid]
|
data = person_data[child_pid]
|
||||||
# Reuse the original additional events to ensure consistency
|
# Reuse the original additional events to ensure consistency
|
||||||
original_additional_events = child_additional_events_map.get(child_pid, [])
|
original_additional_events = child_additional_events_map.get(child_pid, [])
|
||||||
child_person, _, _, _, _ = gen_person(
|
child_person, _, _, _, _, _ = gen_person(
|
||||||
child_pid, data.name, data.surname, data.birth, data.death, data.gender,
|
child_pid, data.name, data.surname, data.birth, data.death, data.gender,
|
||||||
parentin_families=data.parentin, childof_families=data.childof,
|
parentin_families=data.parentin, childof_families=data.childof,
|
||||||
reuse_additional_events=original_additional_events
|
reuse_additional_events=original_additional_events
|
||||||
@ -620,7 +780,7 @@ def main() -> None:
|
|||||||
|
|
||||||
# Create complete XML document
|
# Create complete XML document
|
||||||
people = [father_person, mother_person] + children + grandchildren
|
people = [father_person, mother_person] + children + grandchildren
|
||||||
tree = create_gramps_xml_document(all_events, people, families)
|
tree = create_gramps_xml_document(all_events, people, families, all_media)
|
||||||
|
|
||||||
# Write XML file with proper formatting
|
# Write XML file with proper formatting
|
||||||
# ET.indent is only available in Python 3.9+, so we'll format manually if needed
|
# ET.indent is only available in Python 3.9+, so we'll format manually if needed
|
||||||
@ -652,6 +812,7 @@ def main() -> None:
|
|||||||
print(f" - Multiple families with marriage events")
|
print(f" - Multiple families with marriage events")
|
||||||
print(f" - Birth and death events for all")
|
print(f" - Birth and death events for all")
|
||||||
print(f" - {len(all_additional_events)} additional events (Baptism, Education, Occupation, etc.)")
|
print(f" - {len(all_additional_events)} additional events (Baptism, Education, Occupation, etc.)")
|
||||||
|
print(f" - {len(all_media)} portraits (generated considering age and gender)")
|
||||||
print(f" - Total events: {total_events}")
|
print(f" - Total events: {total_events}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
84
run_gramps_with_logging.sh
Executable file
84
run_gramps_with_logging.sh
Executable file
@ -0,0 +1,84 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Script to run Gramps with debug logging enabled and monitor log file
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
LOG_DIR="$HOME/snap/gramps/11/.config/gramps/gramps60/logs"
|
||||||
|
LOG_FILE="$LOG_DIR/Gramps60.log"
|
||||||
|
|
||||||
|
echo "=== Running Gramps with Debug Logging ==="
|
||||||
|
echo ""
|
||||||
|
echo "Log file will be created at: $LOG_FILE"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Create logs directory if it doesn't exist
|
||||||
|
mkdir -p "$LOG_DIR"
|
||||||
|
|
||||||
|
# Clear any existing log file to start fresh
|
||||||
|
if [ -f "$LOG_FILE" ]; then
|
||||||
|
echo -e "${YELLOW}Backing up existing log file...${NC}"
|
||||||
|
mv "$LOG_FILE" "${LOG_FILE}.backup.$(date +%Y%m%d_%H%M%S)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}Starting Gramps with debug logging enabled...${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Debug loggers enabled:"
|
||||||
|
echo " - gramps.gen.lib.media (media objects)"
|
||||||
|
echo " - gramps.plugins.import.importxml (XML import)"
|
||||||
|
echo " - gramps.gen.db (database operations)"
|
||||||
|
echo ""
|
||||||
|
echo "After you import the demo_family.gramps file, close Gramps and"
|
||||||
|
echo "run './check_gramps_logs.sh' to analyze the log file."
|
||||||
|
echo ""
|
||||||
|
echo "Press Ctrl+C to stop monitoring (Gramps will continue running)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Create a log file in the current directory (snap confinement may prevent writing to ~/.gramps)
|
||||||
|
LOCAL_LOG="gramps_debug.log"
|
||||||
|
echo "Debug output will also be saved to: $PWD/$LOCAL_LOG"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Run Gramps with debug logging and redirect output
|
||||||
|
gramps --debug=gramps.gen.lib.media \
|
||||||
|
--debug=gramps.plugins.import.importxml \
|
||||||
|
--debug=gramps.gen.db \
|
||||||
|
--debug=gramps.gen.lib \
|
||||||
|
"$@" > "$LOCAL_LOG" 2>&1 &
|
||||||
|
|
||||||
|
GRAMPS_PID=$!
|
||||||
|
|
||||||
|
echo "Gramps started with PID: $GRAMPS_PID"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Monitor log file creation
|
||||||
|
echo "Waiting for log file to be created..."
|
||||||
|
while [ ! -f "$LOG_FILE" ] && kill -0 "$GRAMPS_PID" 2>/dev/null; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -f "$LOG_FILE" ]; then
|
||||||
|
echo -e "${GREEN}Log file created!${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Monitoring log file. Press Ctrl+C to stop monitoring."
|
||||||
|
echo "(Gramps will continue running)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Monitor log file for new entries
|
||||||
|
tail -f "$LOG_FILE" 2>/dev/null || {
|
||||||
|
echo "Waiting for log entries..."
|
||||||
|
sleep 2
|
||||||
|
if [ -f "$LOG_FILE" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "Current log contents:"
|
||||||
|
cat "$LOG_FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
else
|
||||||
|
echo "Log file not created yet. Gramps may not have started logging."
|
||||||
|
echo "Check if Gramps is running: ps aux | grep gramps"
|
||||||
|
fi
|
||||||
Loading…
x
Reference in New Issue
Block a user