2 Commitit

Tekijä SHA1 Viesti Päivämäärä
  Andreas Demmelbauer e21ecab2ec Add photos archiver script and update .gitignore to include photos-archive directory 4 viikkoa sitten
  Andreas Demmelbauer ed1be95e82 Remove obsolete schema.sql file to clean up the repository 4 viikkoa sitten
3 muutettua tiedostoa jossa 874 lisäystä ja 74 poistoa
  1. +1
    -0
      .gitignore
  2. +873
    -0
      photos_archiver.py
  3. +0
    -74
      schema.sql

+ 1
- 0
.gitignore Näytä tiedosto

@@ -1,5 +1,6 @@
export/
output/
photos-archive/
iphone-backup/
Messages/
*.sqlite

+ 873
- 0
photos_archiver.py Näytä tiedosto

@@ -0,0 +1,873 @@
#!/usr/bin/env python3

# Archives iPhone photos from a local unencrypted backup and generates an HTML gallery.

import argparse
import os
import shutil
import sqlite3
from pathlib import Path
from datetime import datetime
from collections import defaultdict
import struct

def read_exif_date(file_path):
"""Simple EXIF reader to extract date information from image files."""
try:
with open(file_path, 'rb') as f:
# Read file header to determine format
header = f.read(12)
f.seek(0)
if header.startswith(b'\xff\xe1') and b'Exif' in header:
# JPEG with EXIF
return _read_jpeg_exif_date(f)
elif header.startswith(b'\xff\xd8'):
# JPEG - scan for EXIF segment
return _scan_jpeg_exif_date(f)
elif header[4:8] == b'ftyp':
# HEIC format - basic attempt
return _read_heic_exif_date(f)
elif header.startswith(b'\x89PNG'):
# PNG format
return _read_png_exif_date(f)
except Exception:
pass
return None

def _read_jpeg_exif_date(f):
"""Read EXIF date from JPEG file."""
f.seek(0)
# Find EXIF segment
while True:
marker = f.read(2)
if not marker or marker[0] != 0xff:
break
if marker == b'\xff\xe1': # APP1 segment (EXIF)
length = struct.unpack('>H', f.read(2))[0]
exif_data = f.read(length - 2)
if exif_data.startswith(b'Exif\x00\x00'):
return _parse_exif_data(exif_data[6:])
else:
# Skip other segments
if marker[1] in [0xd8, 0xd9]: # SOI, EOI
continue
try:
length = struct.unpack('>H', f.read(2))[0]
f.seek(length - 2, 1)
except:
break
return None

def _scan_jpeg_exif_date(f):
"""Scan JPEG file for EXIF segment."""
f.seek(0)
data = f.read(65536) # Read first 64KB
# Look for EXIF marker
exif_pos = data.find(b'Exif\x00\x00')
if exif_pos > 0:
return _parse_exif_data(data[exif_pos + 6:])
return None

def _read_heic_exif_date(f):
"""Basic HEIC EXIF reading - simplified approach."""
f.seek(0)
# Read a larger chunk to find EXIF data
data = f.read(2 * 1024 * 1024) # 2MB should be enough for metadata
# Look for EXIF marker in HEIC - try multiple patterns
patterns = [b'Exif\x00\x00', b'Exif\x00\x01', b'EXIF\x00\x00']
for pattern in patterns:
exif_pos = data.find(pattern)
if exif_pos >= 0:
# Try to parse EXIF data starting after the marker
try:
result = _parse_exif_data(data[exif_pos + len(pattern):])
if result:
return result
except:
continue
# Alternative: look for datetime strings directly in the file
return _scan_for_datetime_strings(data)

def _scan_for_datetime_strings(data):
"""Scan binary data for datetime strings."""
import re
try:
# Convert to string for regex search, ignoring decode errors
text = data.decode('ascii', errors='ignore')
# Look for datetime patterns like "2024:08:15 14:30:45"
datetime_pattern = r'20\d{2}:\d{2}:\d{2}\s+\d{2}:\d{2}:\d{2}'
matches = re.findall(datetime_pattern, text)
if matches:
# Return the first valid datetime found
return matches[0]
except:
pass
return None

def _read_png_exif_date(f):
"""Read EXIF date from PNG file."""
f.seek(8) # Skip PNG signature
while True:
try:
# Read chunk length and type
length_data = f.read(4)
if len(length_data) != 4:
break
length = struct.unpack('>I', length_data)[0]
chunk_type = f.read(4)
if len(chunk_type) != 4:
break
if chunk_type == b'eXIf':
# PNG EXIF chunk - contains standard EXIF data
exif_data = f.read(length)
return _parse_exif_data(exif_data)
elif chunk_type == b'iTXt':
# International text chunk - might contain date
chunk_data = f.read(length)
try:
# iTXt format: keyword\0compression\0language\0translated_keyword\0text
parts = chunk_data.split(b'\0', 4)
if len(parts) >= 5:
keyword = parts[0].decode('latin-1', errors='ignore')
text = parts[4].decode('utf-8', errors='ignore')
# Look for date-related keywords
if keyword.lower() in ['date', 'datetime', 'creation time', 'date:create', 'exif:datetime']:
# Try to parse as datetime
import re
datetime_match = re.search(r'20\d{2}[:-]\d{2}[:-]\d{2}[\sT]\d{2}:\d{2}:\d{2}', text)
if datetime_match:
date_str = datetime_match.group()
# Convert to EXIF format
date_str = date_str.replace('-', ':').replace('T', ' ')
return date_str
except:
pass
elif chunk_type == b'tEXt':
# Text chunk - might contain date
chunk_data = f.read(length)
try:
# tEXt format: keyword\0text
null_pos = chunk_data.find(b'\0')
if null_pos > 0:
keyword = chunk_data[:null_pos].decode('latin-1', errors='ignore')
text = chunk_data[null_pos+1:].decode('latin-1', errors='ignore')
if keyword.lower() in ['date', 'creation time', 'timestamp']:
import re
datetime_match = re.search(r'20\d{2}[:-]\d{2}[:-]\d{2}[\sT]\d{2}:\d{2}:\d{2}', text)
if datetime_match:
date_str = datetime_match.group()
date_str = date_str.replace('-', ':').replace('T', ' ')
return date_str
except:
pass
else:
# Skip other chunk types
f.seek(length, 1)
# Skip CRC
f.seek(4, 1)
except (struct.error, OSError):
break
return None

def _parse_exif_data(exif_data):
"""Parse EXIF data to extract date tags."""
if len(exif_data) < 8:
return None
try:
# Check byte order
if exif_data[:2] == b'II':
endian = '<' # Little endian
elif exif_data[:2] == b'MM':
endian = '>' # Big endian
else:
return None
# Get IFD offset
ifd_offset = struct.unpack(endian + 'I', exif_data[4:8])[0]
if ifd_offset >= len(exif_data):
return None
# Read IFD entries
date_tags = {
0x9003: 'DateTimeOriginal', # EXIF DateTimeOriginal
0x0132: 'DateTime', # Image DateTime
0x9004: 'DateTimeDigitized', # EXIF DateTimeDigitized
0x0306: 'DateTime', # Additional DateTime tag
}
# Try to find date in IFD0
date_value = _read_ifd_dates(exif_data, ifd_offset, endian, date_tags)
if date_value:
return date_value
# Try EXIF sub-IFD if available
exif_ifd_offset = _find_exif_ifd(exif_data, ifd_offset, endian)
if exif_ifd_offset and exif_ifd_offset < len(exif_data):
date_value = _read_ifd_dates(exif_data, exif_ifd_offset, endian, date_tags)
if date_value:
return date_value
# Try IFD1 (thumbnail) if available
ifd1_offset = _get_next_ifd(exif_data, ifd_offset, endian)
if ifd1_offset and ifd1_offset < len(exif_data):
date_value = _read_ifd_dates(exif_data, ifd1_offset, endian, date_tags)
if date_value:
return date_value
except Exception:
pass
return None

def _read_ifd_dates(exif_data, ifd_offset, endian, date_tags):
"""Read date tags from IFD."""
try:
if ifd_offset + 2 >= len(exif_data):
return None
entry_count = struct.unpack(endian + 'H', exif_data[ifd_offset:ifd_offset + 2])[0]
for i in range(entry_count):
entry_offset = ifd_offset + 2 + (i * 12)
if entry_offset + 12 > len(exif_data):
break
tag, tag_type, count, value_offset = struct.unpack(
endian + 'HHII', exif_data[entry_offset:entry_offset + 12]
)
if tag in date_tags:
# Handle ASCII string (type 2)
if tag_type == 2:
if count <= 4:
# Value stored in value_offset field
value_data = struct.pack(endian + 'I', value_offset)[:count-1]
else:
# Value stored at offset
if value_offset + count <= len(exif_data):
value_data = exif_data[value_offset:value_offset + count - 1]
else:
continue
try:
date_str = value_data.decode('ascii')
if len(date_str) >= 19 and ':' in date_str: # "YYYY:MM:DD HH:MM:SS"
return date_str
except:
continue
# Handle other types that might contain date strings
elif tag_type in [1, 3, 4, 5]: # BYTE, SHORT, LONG, RATIONAL
try:
if count <= 4:
# Data stored inline
raw_data = struct.pack(endian + 'I', value_offset)
else:
# Data stored at offset
if value_offset + count * 4 <= len(exif_data):
raw_data = exif_data[value_offset:value_offset + min(count * 4, 20)]
else:
continue
# Try to decode as ASCII
try:
potential_date = raw_data.decode('ascii', errors='ignore').rstrip('\x00')
if len(potential_date) >= 19 and ':' in potential_date:
return potential_date
except:
pass
except:
continue
except Exception:
pass
return None

def _get_next_ifd(exif_data, ifd_offset, endian):
"""Get the offset of the next IFD."""
try:
if ifd_offset + 2 >= len(exif_data):
return None
entry_count = struct.unpack(endian + 'H', exif_data[ifd_offset:ifd_offset + 2])[0]
next_ifd_offset_pos = ifd_offset + 2 + (entry_count * 12)
if next_ifd_offset_pos + 4 <= len(exif_data):
next_ifd_offset = struct.unpack(endian + 'I', exif_data[next_ifd_offset_pos:next_ifd_offset_pos + 4])[0]
return next_ifd_offset if next_ifd_offset > 0 else None
except Exception:
pass
return None

def _find_exif_ifd(exif_data, ifd_offset, endian):
"""Find EXIF sub-IFD offset."""
try:
if ifd_offset + 2 >= len(exif_data):
return None
entry_count = struct.unpack(endian + 'H', exif_data[ifd_offset:ifd_offset + 2])[0]
for i in range(entry_count):
entry_offset = ifd_offset + 2 + (i * 12)
if entry_offset + 12 > len(exif_data):
break
tag, tag_type, count, value_offset = struct.unpack(
endian + 'HHII', exif_data[entry_offset:entry_offset + 12]
)
if tag == 0x8769: # EXIF IFD tag
return value_offset
except Exception:
pass
return None

def copy_camera_roll(backup_path: Path, output_path: Path):
manifest_db = backup_path / "Manifest.db"
if not manifest_db.exists():
raise FileNotFoundError(f"Manifest.db not found in {backup_path}")

conn = sqlite3.connect(manifest_db)
cursor = conn.cursor()

# Query all files from CameraRollDomain
cursor.execute("""
SELECT fileID, relativePath
FROM Files
WHERE domain = 'CameraRollDomain'
""")

rows = cursor.fetchall()
print(f"Found {len(rows)} CameraRollDomain files")

for file_id, relative_path in rows:
# FileID is stored as 40-char hex. Backup stores it as <first 2>/<full>
src = backup_path / file_id[:2] / file_id
if not src.exists():
print(f"⚠️ Missing file: {src}")
continue

dest = output_path / relative_path
dest.parent.mkdir(parents=True, exist_ok=True)

if not dest.exists():
shutil.copy2(src, dest)
print(f"✅ Copied {relative_path}")
else:
print(f"⏩ Skipped (already exists): {relative_path}")

conn.close()
print("🎉 Backup extraction completed.")


def find_display_file(original_file, metadata_dcim, thumbnails_dcim):
"""Find the best display file (metadata JPG or thumbnail) for an original file."""
base_name = original_file.stem # e.g., "IMG_1105"
# First try to find in metadata
if metadata_dcim.exists():
for folder in metadata_dcim.iterdir():
if folder.is_dir():
metadata_jpg = folder / f"{base_name}.JPG"
if metadata_jpg.exists():
return metadata_jpg, "metadata"
# Fallback to thumbnails - each image has its own directory named with full filename
if thumbnails_dcim.exists():
for dcim_folder in thumbnails_dcim.iterdir():
if dcim_folder.is_dir():
# Look for a directory named after the full original filename
image_dir = dcim_folder / original_file.name
if image_dir.exists() and image_dir.is_dir():
# Find the JPG file inside this directory (usually numbered like 5003.JPG)
for jpg_file in image_dir.glob("*.JPG"):
return jpg_file, "thumbnail"
# If no display file found, use original
return original_file, "original"

def get_all_original_files(original_dcim):
"""Get all original image/video files from DCIM folders."""
original_files = []
for folder in original_dcim.iterdir():
if not folder.is_dir():
continue
for ext in ['.HEIC', '.JPG', '.PNG', '.MOV', '.MP4', '.JPEG']:
for file_path in folder.glob(f"*{ext}"):
original_files.append(file_path)
return original_files

def get_file_info(file_path):
"""Get file information including size, modification time, and date taken from EXIF."""
stat = file_path.stat()
# Try to get date taken from EXIF data using our custom reader
date_taken = None
date_taken_obj = None
if file_path.suffix.lower() in ['.jpg', '.jpeg', '.heic', '.png']:
try:
exif_date = read_exif_date(file_path)
if exif_date:
try:
date_taken_obj = datetime.strptime(exif_date, '%Y:%m:%d %H:%M:%S')
date_taken = date_taken_obj.strftime('%Y-%m-%d %H:%M:%S')
except ValueError:
pass
except Exception:
pass # Ignore errors reading EXIF data
# Fallback to file modification time if no EXIF date
if not date_taken_obj:
date_taken_obj = datetime.fromtimestamp(stat.st_mtime)
date_taken = date_taken_obj.strftime('%Y-%m-%d %H:%M:%S')
return {
'size': stat.st_size,
'date_taken': date_taken,
'date_taken_obj': date_taken_obj,
'size_mb': round(stat.st_size / (1024 * 1024), 2)
}

def generate_gallery(photos_root: Path):
"""Generate the HTML image gallery."""
html_view = photos_root / "html_view"
# Paths for different file types
metadata_dcim = photos_root / "Media" / "PhotoData" / "Metadata" / "DCIM"
thumbnails_dcim = photos_root / "Media" / "PhotoData" / "Thumbnails" / "V2" / "DCIM"
original_dcim = photos_root / "Media" / "DCIM"
if not original_dcim.exists():
print(f"❌ Original DCIM folder not found: {original_dcim}")
return
print(f"📁 Looking for display files in:")
print(f" Metadata: {metadata_dcim.exists() and 'Found' or 'Not found'}")
print(f" Thumbnails: {thumbnails_dcim.exists() and 'Found' or 'Not found'}")
# Get all original files
original_files = get_all_original_files(original_dcim)
print(f"Found {len(original_files)} original files")
if not original_files:
print("❌ No images found to generate gallery")
return
# Collect all images
images = []
metadata_count = 0
thumbnail_count = 0
original_only_count = 0
for original_file in original_files:
# Find the best display file
display_file, display_type = find_display_file(original_file, metadata_dcim, thumbnails_dcim)
# Count display types
if display_type == "metadata":
metadata_count += 1
elif display_type == "thumbnail":
thumbnail_count += 1
else:
original_only_count += 1
# Get file info
original_info = get_file_info(original_file)
display_info = get_file_info(display_file) if display_file != original_file else original_info
# Get folder name from original file path
folder_name = original_file.parent.name
images.append({
'name': original_file.stem,
'display_path': str(display_file.relative_to(photos_root)),
'original_path': str(original_file.relative_to(photos_root)),
'folder': folder_name,
'display_info': display_info,
'original_info': original_info,
'original_ext': original_file.suffix.upper(),
'display_type': display_type,
'display_ext': display_file.suffix.upper()
})
print(f"📊 Total original files: {len(original_files)}")
print(f"📊 Using metadata for display: {metadata_count}")
print(f"📊 Using thumbnails for display: {thumbnail_count}")
print(f"📊 Using original for display: {original_only_count}")
print(f"📊 Images to display: {len(images)}")
# Sort images by date taken (newest first), then by name
images.sort(key=lambda x: (x['original_info']['date_taken_obj'], x['name']), reverse=True)
# Group images by date
grouped_images = defaultdict(list)
for img in images:
date_key = img['original_info']['date_taken_obj'].strftime('%Y-%m-%d')
grouped_images[date_key].append(img)
# Generate HTML content
html_content = generate_html_content(images, grouped_images)
# Write the HTML file
html_view.mkdir(exist_ok=True)
output_file = html_view / "index.html"
with open(output_file, 'w', encoding='utf-8') as f:
f.write(html_content)
print(f"✅ Gallery generated: {output_file}")
print(f"📊 {len(images)} images included")
print(f"🌐 Open {output_file} in your browser to view the gallery")

def generate_html_content(images, grouped_images):
"""Generate the HTML content for the gallery."""
return f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>iPhone Gallery Archive</title>
<style>
* {{
margin: 0;
padding: 0;
box-sizing: border-box;
}}
body {{
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
color: #333;
}}
.header {{
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 2rem;
text-align: center;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}}
.header h1 {{
font-size: 2.5rem;
margin-bottom: 0.5rem;
}}
.stats {{
margin-top: 1rem;
opacity: 0.9;
}}
.gallery-container {{
margin: 2rem auto;
padding: 0 1rem;
}}
.date-section {{
margin-bottom: 2rem;
}}
.date-header {{
color: #495057;
border-top: 1px solid #ddd;
padding: 1rem 0 0;
margin-bottom: 1rem;
font-size: 1rem;
}}
.date-subheader {{
font-size: 0.9rem;
opacity: 0.7;
margin-top: 0.25rem;
}}
.gallery {{
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}}
.image-card {{
overflow: hidden;
}}
.image-card .date-taken {{
margin-top: 0.5rem;
font-size: 0.85rem;
}}
.image-card .file-path {{
font-size: 0.75rem;
margin-bottom: 0.25rem;
word-break: break-all;
}}
.image-container {{
position: relative;
width: 100%;
overflow: hidden;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
line-height: 0;
border-radius: 8px;
}}
@keyframes loading {{
0% {{
background-position: 200% 0;
}}
100% {{
background-position: -200% 0;
}}
}}
.image-container img {{
width: 100%;
height: auto;
object-fit: contain;
transition: transform 0.3s ease, opacity 0.3s ease;
background: transparent;
line-height: 0;
border-radius: 8px;
}}
.image-container img[loading="lazy"]:not([src]) {{
opacity: 0;
}}
.image-container img[loading="lazy"] {{
opacity: 1;
}}
.image-container:has(img[loading="lazy"][src]) {{
background: white;
animation: none;
}}
.image-card:hover .image-container img {{
transform: scale(1.02);
}}
.video-indicator {{
position: absolute;
top: 0.5rem;
right: 0.5rem;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 0.3rem 0.5rem;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 0.2rem;
line-height: 1.2;
}}
.overlay {{
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(to bottom, transparent 40%, rgba(0,0,0,0.9));
opacity: 0;
transition: opacity 0.3s ease;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 1rem;
}}
.image-card:hover .overlay {{
opacity: 1;
}}
.overlay-content {{
color: white;
margin-bottom: 1rem;
}}
.overlay-content .filename {{
font-weight: 600;
margin-bottom: 0.5rem;
}}
.overlay-content .details {{
opacity: 0.9;
line-height: 1.4;
}}
.overlay-buttons {{
display: flex;
gap: 0.5rem;
}}
.btn {{
padding: 0.6rem 1rem;
border: none;
border-radius: 6px;
text-decoration: none;
text-align: center;
transition: all 0.3s ease;
cursor: pointer;
font-size: 0.9rem;
width: 100%;
line-height: 1.2;
}}
.btn-primary {{
background: rgba(255, 255, 255, 0.95);
color: #333;
}}
.btn-primary:hover {{
background: white;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}}
.folder-badge {{
display: inline-block;
background: #e9ecef;
color: #495057;
padding: 0.2rem 0.5rem;
border-radius: 12px;
font-size: 0.8rem;
font-weight: 600;
}}
@media (max-width: 768px) {{
.gallery {{
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 0.75rem;
}}
.header h1 {{
font-size: 2rem;
}}
.overlay {{
padding: 0.75rem;
}}
}}
</style>
</head>
<body>
<div class="header">
<h1>📸 iPhone Camera Roll Gallery</h1>
<p>Extracted from iPhone backup</p>
<div class="stats">
{len(images)} photos • {len(set(img['folder'] for img in images))} folders
</div>
</div>
<div class="gallery-container">
{generate_gallery_sections(grouped_images)}
</div>
</body>
</html>"""

def generate_gallery_sections(grouped_images):
"""Generate HTML for gallery sections grouped by date."""
sections_html = ""
for date_key in sorted(grouped_images.keys(), reverse=True):
date_obj = datetime.strptime(date_key, '%Y-%m-%d')
date_display = date_obj.strftime('%d.%m.%Y')
image_count = len(grouped_images[date_key])
sections_html += f"""
<div class="date-section">
<div class="date-header">
{date_display}
<div class="date-subheader">{image_count} {'photo' if image_count == 1 else 'photos'}</div>
</div>
<div class="gallery">
"""
for img in grouped_images[date_key]:
# Check if it's a video file
is_video = img['original_ext'].lower() in ['.mov', '.mp4']
video_indicator = '<div class="video-indicator">🎬 VIDEO</div>' if is_video else ''
sections_html += f"""
<div class="image-card">
<div class="image-container">
<img src="../{img['display_path']}" alt="{img['name']}" loading="lazy" decoding="async">
{video_indicator}
<div class="overlay">
<div class="overlay-content">
<div class="details">
<div class="file-path">{img['original_path']}</div>"""
# Add date taken if available
if img['original_info']['date_taken']:
sections_html += f"""
<div class="date-taken">{img['original_info']['date_taken']}</div>"""
sections_html += f"""
</div>
</div>
<div class="overlay-buttons">
<a href="../{img['original_path']}" target="_blank" class="btn btn-primary">
Original ({img['original_info']['size_mb']} MB)
</a>
</div>
</div>
</div>
</div>
"""
sections_html += """
</div>
</div>
"""
return sections_html


def main():
parser = argparse.ArgumentParser(description="Extract Camera Roll from iPhone backup and optionally generate HTML gallery")
parser.add_argument("--backup-path", required=True, type=Path,
help="Path to iPhone backup folder (with Manifest.db)")
parser.add_argument("--output-path", required=True, type=Path,
help="Path where Camera Roll should be restored")
parser.add_argument("--generate-gallery", action="store_true",
help="Generate HTML gallery after extraction")

args = parser.parse_args()

# Extract camera roll
copy_camera_roll(args.backup_path, args.output_path)
# Generate gallery if requested
if args.generate_gallery:
print("\n🖼️ Generating HTML gallery...")
generate_gallery(args.output_path)


if __name__ == "__main__":
main()

+ 0
- 74
schema.sql Näytä tiedosto

@@ -1,74 +0,0 @@
CREATE TABLE ZWABLACKLISTITEM ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZJID VARCHAR );
CREATE TABLE ZWACHATPROPERTIES ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZALERTS INTEGER, ZENABLED INTEGER, ZCHATSESSION INTEGER, ZMUTEDATE TIMESTAMP, ZSOUNDNAME VARCHAR );
CREATE TABLE ZWACHATPUSHCONFIG ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZALERTS INTEGER, ZMUTEDUNTIL TIMESTAMP, ZJID VARCHAR, ZRINGTONE VARCHAR, ZSOUND VARCHAR );
CREATE TABLE ZWACHATSESSION ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZARCHIVED INTEGER, ZCONTACTABID INTEGER, ZFLAGS INTEGER, ZHIDDEN INTEGER, ZIDENTITYVERIFICATIONEPOCH INTEGER, ZIDENTITYVERIFICATIONSTATE INTEGER, ZMESSAGECOUNTER INTEGER, ZREMOVED INTEGER, ZSESSIONTYPE INTEGER, ZSPOTLIGHTSTATUS INTEGER, ZUNREADCOUNT INTEGER, ZGROUPINFO INTEGER, ZLASTMESSAGE INTEGER, ZPROPERTIES INTEGER, ZLASTMESSAGEDATE TIMESTAMP, ZLOCATIONSHARINGENDDATE TIMESTAMP, ZCONTACTIDENTIFIER VARCHAR, ZCONTACTJID VARCHAR, ZETAG VARCHAR, ZLASTMESSAGETEXT VARCHAR, ZPARTNERNAME VARCHAR, ZSAVEDINPUT VARCHAR );
CREATE TABLE ZWAGROUPINFO ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZSTATE INTEGER, ZCHATSESSION INTEGER, ZLASTMESSAGEOWNER INTEGER, ZCREATIONDATE TIMESTAMP, ZSUBJECTTIMESTAMP TIMESTAMP, ZCREATORJID VARCHAR, ZOWNERJID VARCHAR, ZPICTUREID VARCHAR, ZPICTUREPATH VARCHAR, ZSOURCEJID VARCHAR, ZSUBJECTOWNERJID VARCHAR );
CREATE TABLE ZWAGROUPMEMBER ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZCONTACTABID INTEGER, ZISACTIVE INTEGER, ZISADMIN INTEGER, ZSENDERKEYSENT INTEGER, ZCHATSESSION INTEGER, ZRECENTGROUPCHAT INTEGER, ZCONTACTIDENTIFIER VARCHAR, ZCONTACTNAME VARCHAR, ZFIRSTNAME VARCHAR, ZMEMBERJID VARCHAR );
CREATE TABLE ZWAGROUPMEMBERSCHANGE ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZCHANGETYPE INTEGER, ZCHANGEDATE TIMESTAMP, ZGROUPJID VARCHAR, ZMEMBERJIDS VARCHAR, ZPHASHBEFORECHANGE VARCHAR );
CREATE TABLE ZWAMEDIAITEM ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZCLOUDSTATUS INTEGER, ZFILESIZE INTEGER, ZMEDIAORIGIN INTEGER, ZMOVIEDURATION INTEGER, ZMESSAGE INTEGER, ZASPECTRATIO FLOAT, ZHACCURACY FLOAT, ZLATITUDE FLOAT, ZLONGITUDE FLOAT, ZMEDIAURLDATE TIMESTAMP, ZAUTHORNAME VARCHAR, ZCOLLECTIONNAME VARCHAR, ZMEDIALOCALPATH VARCHAR, ZMEDIAURL VARCHAR, ZTHUMBNAILLOCALPATH VARCHAR, ZTITLE VARCHAR, ZVCARDNAME VARCHAR, ZVCARDSTRING VARCHAR, ZXMPPTHUMBPATH VARCHAR, ZMEDIAKEY BLOB, ZMETADATA BLOB );
CREATE TABLE ZWAMESSAGE ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZCHILDMESSAGESDELIVEREDCOUNT INTEGER, ZCHILDMESSAGESPLAYEDCOUNT INTEGER, ZCHILDMESSAGESREADCOUNT INTEGER, ZDATAITEMVERSION INTEGER, ZDOCID INTEGER, ZENCRETRYCOUNT INTEGER, ZFILTEREDRECIPIENTCOUNT INTEGER, ZFLAGS INTEGER, ZGROUPEVENTTYPE INTEGER, ZISFROMME INTEGER, ZMESSAGEERRORSTATUS INTEGER, ZMESSAGESTATUS INTEGER, ZMESSAGETYPE INTEGER, ZSORT INTEGER, ZSPOTLIGHTSTATUS INTEGER, ZSTARRED INTEGER, ZCHATSESSION INTEGER, ZGROUPMEMBER INTEGER, ZLASTSESSION INTEGER, ZMEDIAITEM INTEGER, ZMESSAGEINFO INTEGER, ZPARENTMESSAGE INTEGER, ZMESSAGEDATE TIMESTAMP, ZSENTDATE TIMESTAMP, ZFROMJID VARCHAR, ZMEDIASECTIONID VARCHAR, ZPHASH VARCHAR, ZPUSHNAME VARCHAR, ZSTANZAID VARCHAR, ZTEXT VARCHAR, ZTOJID VARCHAR );
CREATE TABLE ZWAMESSAGEDATAITEM ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZINDEX INTEGER, ZOWNSTHUMBNAIL INTEGER, ZTYPE INTEGER, ZMESSAGE INTEGER, ZDATE TIMESTAMP, ZCHATJID VARCHAR, ZCONTENT1 VARCHAR, ZCONTENT2 VARCHAR, ZMATCHEDTEXT VARCHAR, ZSECTIONID VARCHAR, ZSENDERJID VARCHAR, ZSUMMARY VARCHAR, ZTHUMBNAILPATH VARCHAR, ZTITLE VARCHAR );
CREATE TABLE ZWAMESSAGEINFO ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZMESSAGE INTEGER, ZRECEIPTINFO BLOB );
CREATE TABLE ZWAPROFILEPICTUREITEM ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZREQUESTDATE TIMESTAMP, ZJID VARCHAR, ZPATH VARCHAR, ZPICTUREID VARCHAR );
CREATE TABLE ZWAPROFILEPUSHNAME ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZJID VARCHAR, ZPUSHNAME VARCHAR );
CREATE TABLE ZWAVCARDMENTION ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZISFROMME INTEGER, ZTRUSTEDWHENINDEXED INTEGER, ZMEDIAITEM INTEGER, ZDATE TIMESTAMP, ZSENDERJID VARCHAR, ZWHATSAPPID VARCHAR );
CREATE TABLE Z_METADATA (Z_VERSION INTEGER PRIMARY KEY, Z_UUID VARCHAR(255), Z_PLIST BLOB);
CREATE TABLE Z_MODELCACHE (Z_CONTENT BLOB);
CREATE TABLE ZWAZ1PAYMENTTRANSACTION ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZAMOUNT_1000 INTEGER, ZSTATUS INTEGER, ZTYPE INTEGER, ZTIMESTAMP TIMESTAMP, ZBANKTRANSACTIONID VARCHAR, ZCREDENTIALID VARCHAR, ZCURRENCY VARCHAR, ZERRORCODE VARCHAR, ZGROUPJID VARCHAR, ZMESSAGESTANZAID VARCHAR, ZRECEIVERJID VARCHAR, ZSENDERJID VARCHAR, ZTRANSACTIONID VARCHAR, ZMETADATA BLOB );
CREATE TABLE Z_PRIMARYKEY (Z_ENT INTEGER PRIMARY KEY, Z_NAME VARCHAR, Z_SUPER INTEGER, Z_MAX INTEGER);
CREATE INDEX ZWACHATPROPERTIES_ZCHATSESSION_INDEX ON ZWACHATPROPERTIES (ZCHATSESSION);
CREATE INDEX ZWACHATSESSION_ZGROUPINFO_INDEX ON ZWACHATSESSION (ZGROUPINFO);
CREATE INDEX ZWACHATSESSION_ZLASTMESSAGE_INDEX ON ZWACHATSESSION (ZLASTMESSAGE);
CREATE INDEX ZWACHATSESSION_ZPROPERTIES_INDEX ON ZWACHATSESSION (ZPROPERTIES);
CREATE INDEX ZWAGROUPINFO_ZCHATSESSION_INDEX ON ZWAGROUPINFO (ZCHATSESSION);
CREATE INDEX ZWAGROUPINFO_ZLASTMESSAGEOWNER_INDEX ON ZWAGROUPINFO (ZLASTMESSAGEOWNER);
CREATE INDEX ZWAGROUPMEMBER_ZCHATSESSION_INDEX ON ZWAGROUPMEMBER (ZCHATSESSION);
CREATE INDEX ZWAGROUPMEMBER_ZRECENTGROUPCHAT_INDEX ON ZWAGROUPMEMBER (ZRECENTGROUPCHAT);
CREATE INDEX ZWAMEDIAITEM_ZMESSAGE_INDEX ON ZWAMEDIAITEM (ZMESSAGE);
CREATE INDEX ZWAMESSAGE_ZCHATSESSION_INDEX ON ZWAMESSAGE (ZCHATSESSION);
CREATE INDEX ZWAMESSAGE_ZGROUPMEMBER_INDEX ON ZWAMESSAGE (ZGROUPMEMBER);
CREATE INDEX ZWAMESSAGE_ZLASTSESSION_INDEX ON ZWAMESSAGE (ZLASTSESSION);
CREATE INDEX ZWAMESSAGE_ZMEDIAITEM_INDEX ON ZWAMESSAGE (ZMEDIAITEM);
CREATE INDEX ZWAMESSAGE_ZMESSAGEINFO_INDEX ON ZWAMESSAGE (ZMESSAGEINFO);
CREATE INDEX ZWAMESSAGE_ZPARENTMESSAGE_INDEX ON ZWAMESSAGE (ZPARENTMESSAGE);
CREATE INDEX ZWAMESSAGE_ZCHATSESSION_ZSORT ON ZWAMESSAGE (ZCHATSESSION, ZSORT);
CREATE INDEX ZWAMESSAGE_ZCHATSESSION_ZMEDIASECTIONID_ZSORT ON ZWAMESSAGE (ZCHATSESSION, ZMEDIASECTIONID, ZSORT);
CREATE INDEX ZWAMESSAGE_ZCHATSESSION_ZSTARRED_ZMESSAGEDATE ON ZWAMESSAGE (ZCHATSESSION, ZSTARRED, ZMESSAGEDATE);
CREATE INDEX ZWAMESSAGEDATAITEM_ZMESSAGE_INDEX ON ZWAMESSAGEDATAITEM (ZMESSAGE);
CREATE INDEX ZWAMESSAGEINFO_ZMESSAGE_INDEX ON ZWAMESSAGEINFO (ZMESSAGE);
CREATE INDEX ZWAVCARDMENTION_ZMEDIAITEM_INDEX ON ZWAVCARDMENTION (ZMEDIAITEM);
CREATE INDEX Z_WAChatSession_contactJID ON ZWACHATSESSION (ZCONTACTJID COLLATE BINARY ASC);
CREATE INDEX Z_WAChatSession_identityVerificationEpoch ON ZWACHATSESSION (ZIDENTITYVERIFICATIONEPOCH COLLATE BINARY ASC);
CREATE INDEX Z_WAChatSession_identityVerificationState ON ZWACHATSESSION (ZIDENTITYVERIFICATIONSTATE COLLATE BINARY ASC);
CREATE INDEX Z_WAChatSession_lastMessageDate ON ZWACHATSESSION (ZLASTMESSAGEDATE COLLATE BINARY ASC);
CREATE INDEX Z_WAChatSession_removed ON ZWACHATSESSION (ZREMOVED COLLATE BINARY ASC);
CREATE INDEX Z_WAChatSession_sessionType ON ZWACHATSESSION (ZSESSIONTYPE COLLATE BINARY ASC);
CREATE INDEX Z_WAChatSession_spotlightStatus ON ZWACHATSESSION (ZSPOTLIGHTSTATUS COLLATE BINARY ASC);
CREATE INDEX Z_WAGroupMember_memberJID ON ZWAGROUPMEMBER (ZMEMBERJID COLLATE BINARY ASC);
CREATE INDEX Z_WAGroupMembersChange_changeDate ON ZWAGROUPMEMBERSCHANGE (ZCHANGEDATE COLLATE BINARY ASC);
CREATE INDEX Z_WAGroupMembersChange_changeType ON ZWAGROUPMEMBERSCHANGE (ZCHANGETYPE COLLATE BINARY ASC);
CREATE INDEX Z_WAGroupMembersChange_groupJID ON ZWAGROUPMEMBERSCHANGE (ZGROUPJID COLLATE BINARY ASC);
CREATE INDEX Z_WAMediaItem_cloudStatus ON ZWAMEDIAITEM (ZCLOUDSTATUS COLLATE BINARY ASC);
CREATE INDEX Z_WAMediaItem_vCardName ON ZWAMEDIAITEM (ZVCARDNAME COLLATE BINARY ASC);
CREATE INDEX Z_WAMessage_dataItemVersion_messageDate ON ZWAMESSAGE (ZDATAITEMVERSION COLLATE BINARY ASC, ZMESSAGEDATE COLLATE BINARY ASC);
CREATE INDEX Z_WAMessage_docID ON ZWAMESSAGE (ZDOCID COLLATE BINARY ASC);
CREATE INDEX Z_WAMessage_messageDate ON ZWAMESSAGE (ZMESSAGEDATE COLLATE BINARY ASC);
CREATE INDEX Z_WAMessage_messageErrorStatus ON ZWAMESSAGE (ZMESSAGEERRORSTATUS COLLATE BINARY ASC);
CREATE INDEX Z_WAMessage_messageStatus ON ZWAMESSAGE (ZMESSAGESTATUS COLLATE BINARY ASC);
CREATE INDEX Z_WAMessage_messageType ON ZWAMESSAGE (ZMESSAGETYPE COLLATE BINARY ASC);
CREATE INDEX Z_WAMessage_sort ON ZWAMESSAGE (ZSORT COLLATE BINARY ASC);
CREATE INDEX Z_WAMessage_spotlightStatus_messageDate ON ZWAMESSAGE (ZSPOTLIGHTSTATUS COLLATE BINARY ASC, ZMESSAGEDATE COLLATE BINARY ASC);
CREATE INDEX Z_WAMessage_stanzaID ON ZWAMESSAGE (ZSTANZAID COLLATE BINARY ASC);
CREATE INDEX Z_WAMessage_starred ON ZWAMESSAGE (ZSTARRED COLLATE BINARY ASC);
CREATE INDEX Z_WAMessageDataItem_chatJID_type_sectionID_date ON ZWAMESSAGEDATAITEM (ZCHATJID COLLATE BINARY ASC, ZTYPE COLLATE BINARY ASC, ZSECTIONID COLLATE BINARY ASC, ZDATE COLLATE BINARY ASC);
CREATE INDEX Z_WAMessageDataItem_senderJID_type_sectionID_date ON ZWAMESSAGEDATAITEM (ZSENDERJID COLLATE BINARY ASC, ZTYPE COLLATE BINARY ASC, ZSECTIONID COLLATE BINARY ASC, ZDATE COLLATE BINARY ASC);
CREATE INDEX Z_WAProfilePictureItem_jid ON ZWAPROFILEPICTUREITEM (ZJID COLLATE BINARY ASC);
CREATE INDEX Z_WAProfilePushName_jid ON ZWAPROFILEPUSHNAME (ZJID COLLATE BINARY ASC);
CREATE INDEX Z_WAVCardMention_date ON ZWAVCARDMENTION (ZDATE COLLATE BINARY ASC);
CREATE INDEX Z_WAVCardMention_whatsAppID ON ZWAVCARDMENTION (ZWHATSAPPID COLLATE BINARY ASC);
CREATE INDEX Z_WAZ1PaymentTransaction_credentialId ON ZWAZ1PAYMENTTRANSACTION (ZCREDENTIALID COLLATE BINARY ASC);
CREATE INDEX Z_WAZ1PaymentTransaction_messageStanzaId ON ZWAZ1PAYMENTTRANSACTION (ZMESSAGESTANZAID COLLATE BINARY ASC);
CREATE INDEX Z_WAZ1PaymentTransaction_status ON ZWAZ1PAYMENTTRANSACTION (ZSTATUS COLLATE BINARY ASC);
CREATE INDEX Z_WAZ1PaymentTransaction_transactionId ON ZWAZ1PAYMENTTRANSACTION (ZTRANSACTIONID COLLATE BINARY ASC);
CREATE INDEX Z_WAZ1PaymentTransaction_type ON ZWAZ1PAYMENTTRANSACTION (ZTYPE COLLATE BINARY ASC);
CREATE INDEX Z_WAZ1PaymentTransaction_type_status ON ZWAZ1PAYMENTTRANSACTION (ZTYPE COLLATE BINARY ASC, ZSTATUS COLLATE BINARY ASC);

Ladataan…
Peruuta
Tallenna