Skip to content

Commit 85f0dee

Browse files
committed
fix: Refactor
1 parent bec6070 commit 85f0dee

File tree

1 file changed

+82
-56
lines changed

1 file changed

+82
-56
lines changed

src/c2pa/c2pa.py

Lines changed: 82 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,9 +1293,14 @@ def __init__(self,
12931293
contain invalid UTF-8 characters
12941294
"""
12951295

1296+
self._closed = False
12961297
self._reader = None
12971298
self._own_stream = None
12981299

1300+
# This is used to keep track of a file
1301+
# we may have opened ourselves, and that we need to close later
1302+
self._backing_file = None
1303+
12991304
if stream is None:
13001305
# If we don't get a stream as param:
13011306
# Create a stream from the file path in format_or_path
@@ -1342,13 +1347,13 @@ def __init__(self,
13421347
)
13431348

13441349
# Store the file to close it later
1345-
self._file_like_stream = file
1350+
self._backing_file = file
13461351

13471352
except Exception as e:
13481353
if self._own_stream:
13491354
self._own_stream.close()
1350-
if hasattr(self, '_file_like_stream'):
1351-
self._file_like_stream.close()
1355+
if self._backing_file:
1356+
self._backing_file.close()
13521357
raise C2paError.Io(
13531358
Reader._ERROR_MESSAGES['io_error'].format(
13541359
str(e)))
@@ -1402,12 +1407,13 @@ def __init__(self,
14021407
)
14031408
)
14041409

1405-
self._file_like_stream = file
1410+
# File stream we opened and own
1411+
self._backing_file = file
14061412
except Exception as e:
14071413
if self._own_stream:
14081414
self._own_stream.close()
1409-
if hasattr(self, '_file_like_stream'):
1410-
self._file_like_stream.close()
1415+
if self._backing_file:
1416+
self._backing_file.close()
14111417
raise C2paError.Io(
14121418
Reader._ERROR_MESSAGES['io_error'].format(
14131419
str(e)))
@@ -1460,60 +1466,83 @@ def __enter__(self):
14601466
def __exit__(self, exc_type, exc_val, exc_tb):
14611467
self.close()
14621468

1469+
def __del__(self):
1470+
"""Ensure resources are cleaned up if close() wasn't called.
1471+
1472+
This destructor handles cleanup without causing double frees.
1473+
It only cleans up if the object hasn't been explicitly closed.
1474+
"""
1475+
self._cleanup_resources()
1476+
1477+
def _ensure_valid_state(self):
1478+
"""Ensure the reader is in a valid state for operations.
1479+
1480+
Raises:
1481+
C2paError: If the reader is closed or invalid
1482+
"""
1483+
if self._closed or not self._reader:
1484+
raise C2paError("Reader is closed")
1485+
1486+
def _cleanup_resources(self):
1487+
"""Internal cleanup method that releases native resources.
1488+
1489+
This method handles the actual cleanup logic and can be called
1490+
from both close() and __del__ without causing double frees.
1491+
"""
1492+
try:
1493+
# Only cleanup if not already closed and we have a valid reader
1494+
if hasattr(self, '_closed') and not self._closed:
1495+
# Clean up reader
1496+
if hasattr(self, '_reader') and self._reader:
1497+
try:
1498+
_lib.c2pa_reader_free(self._reader)
1499+
except Exception:
1500+
# Cleanup failure doesn't raise exceptions
1501+
pass
1502+
finally:
1503+
self._reader = None
1504+
1505+
# Clean up stream
1506+
if hasattr(self, '_own_stream') and self._own_stream:
1507+
try:
1508+
self._own_stream.close()
1509+
except Exception:
1510+
# Cleanup failure doesn't raise exceptions
1511+
pass
1512+
finally:
1513+
self._own_stream = None
1514+
1515+
# Clean up backing file
1516+
if self._backing_file:
1517+
try:
1518+
self._backing_file.close()
1519+
except Exception:
1520+
# Cleanup failure doesn't raise exceptions
1521+
pass
1522+
finally:
1523+
self._backing_file = None
1524+
1525+
self._closed = True
1526+
except Exception:
1527+
# Ensure we don't raise exceptions during cleanup
1528+
pass
1529+
14631530
def close(self):
1464-
"""Release the reader resources.
1531+
"""Release the reader resources safely.
14651532
14661533
This method ensures all resources are properly cleaned up,
14671534
even if errors occur during cleanup.
14681535
Errors during cleanup are logged but not raised to ensure cleanup.
14691536
Multiple calls to close() are handled gracefully.
14701537
"""
1471-
1472-
# Track if we've already cleaned up
1473-
if not hasattr(self, '_closed'):
1474-
self._closed = False
1475-
14761538
if self._closed:
1477-
return
1539+
return # Already closed, safe to return
14781540

14791541
try:
1480-
# Clean up reader
1481-
if hasattr(self, '_reader') and self._reader:
1482-
try:
1483-
_lib.c2pa_reader_free(self._reader)
1484-
except Exception as e:
1485-
print(
1486-
Reader._ERROR_MESSAGES['reader_cleanup_error'].format(
1487-
str(e)), file=sys.stderr)
1488-
finally:
1489-
self._reader = None
1490-
1491-
# Clean up stream
1492-
if hasattr(self, '_own_stream') and self._own_stream:
1493-
try:
1494-
self._own_stream.close()
1495-
except Exception as e:
1496-
print(
1497-
Reader._ERROR_MESSAGES['stream_error'].format(
1498-
str(e)), file=sys.stderr)
1499-
finally:
1500-
self._own_stream = None
1501-
1502-
# Clean up file
1503-
if hasattr(self, '_file_like_stream'):
1504-
try:
1505-
self._file_like_stream.close()
1506-
except Exception as e:
1507-
print(
1508-
Reader._ERROR_MESSAGES['file_error'].format(
1509-
str(e)), file=sys.stderr)
1510-
finally:
1511-
self._file_like_stream = None
1512-
1513-
# Clear any stored strings
1514-
if hasattr(self, '_strings'):
1515-
self._strings.clear()
1542+
# Use the internal cleanup method
1543+
self._cleanup_resources()
15161544
except Exception as e:
1545+
# Log any unexpected errors during close
15171546
print(
15181547
Reader._ERROR_MESSAGES['cleanup_error'].format(
15191548
str(e)), file=sys.stderr)
@@ -1529,9 +1558,7 @@ def json(self) -> str:
15291558
Raises:
15301559
C2paError: If there was an error getting the JSON
15311560
"""
1532-
1533-
if not self._reader:
1534-
raise C2paError("Reader is closed")
1561+
self._ensure_valid_state()
15351562
result = _lib.c2pa_reader_json(self._reader)
15361563

15371564
if result is None:
@@ -1555,13 +1582,12 @@ def resource_to_stream(self, uri: str, stream: Any) -> int:
15551582
Raises:
15561583
C2paError: If there was an error writing the resource to stream
15571584
"""
1558-
if not self._reader:
1559-
raise C2paError("Reader is closed")
1585+
self._ensure_valid_state()
15601586

1561-
self._uri_str = uri.encode('utf-8')
1587+
uri_str = uri.encode('utf-8')
15621588
with Stream(stream) as stream_obj:
15631589
result = _lib.c2pa_reader_resource_to_stream(
1564-
self._reader, self._uri_str, stream_obj._stream)
1590+
self._reader, uri_str, stream_obj._stream)
15651591

15661592
if result < 0:
15671593
error = _parse_operation_result_for_error(_lib.c2pa_error())

0 commit comments

Comments
 (0)