## demonstrates using BackupSeek to enumerate data streams for a file
import win32file, win32api, win32con
from win32com import storagecon
import pythoncom, pywintypes
import struct, traceback

stream_types = {
    win32con.BACKUP_DATA: "Standard data",
    win32con.BACKUP_EA_DATA: "Extended attribute data",
    win32con.BACKUP_SECURITY_DATA: "Security descriptor data",
    win32con.BACKUP_ALTERNATE_DATA: "Alternative data streams",
    win32con.BACKUP_LINK: "Hard link information",
    win32con.BACKUP_PROPERTY_DATA: "Property data",
    win32con.BACKUP_OBJECT_ID: "Objects identifiers",
    win32con.BACKUP_REPARSE_DATA: "Reparse points",
    win32con.BACKUP_SPARSE_BLOCK: "Sparse file",
}

tempdir = win32api.GetTempPath()
tempfile = win32api.GetTempFileName(tempdir, "bkr")[0]
print("Filename:", tempfile)

f = open(tempfile, "w")
f.write("some random junk" + "x" * 100)
f.close()

f = open(tempfile + ":streamdata", "w")
f.write("data written to alternate stream" + "y" * 100)
f.close()

f = open(tempfile + ":anotherstream", "w")
f.write("z" * 200)
f.close()

## add Summary Information, which is stored as a separate stream
m = storagecon.STGM_READWRITE | storagecon.STGM_SHARE_EXCLUSIVE | storagecon.STGM_DIRECT
pss = pythoncom.StgOpenStorageEx(
    tempfile, m, storagecon.STGFMT_FILE, 0, pythoncom.IID_IPropertySetStorage, None
)
ps = pss.Create(
    pythoncom.FMTID_SummaryInformation,
    pythoncom.IID_IPropertyStorage,
    0,
    storagecon.STGM_READWRITE | storagecon.STGM_SHARE_EXCLUSIVE,
)
ps.WriteMultiple(
    (storagecon.PIDSI_KEYWORDS, storagecon.PIDSI_COMMENTS), ("keywords", "comments")
)
ps = None
pss = None

sa = pywintypes.SECURITY_ATTRIBUTES()
sa.bInheritHandle = False
h = win32file.CreateFile(
    tempfile,
    win32con.GENERIC_ALL,
    win32con.FILE_SHARE_READ,
    sa,
    win32con.OPEN_EXISTING,
    win32file.FILE_FLAG_BACKUP_SEMANTICS,
    None,
)


""" stream header:
typedef struct _WIN32_STREAM_ID {
    DWORD dwStreamId;  DWORD dwStreamAttributes;  LARGE_INTEGER Size;
    DWORD dwStreamNameSize;  WCHAR cStreamName[ANYSIZE_ARRAY];
}
"""

win32_stream_id_format = "LLQL"
win32_stream_id_size = struct.calcsize(win32_stream_id_format)


def parse_stream_header(h, ctxt, data):
    stream_type, stream_attributes, stream_size, stream_name_size = struct.unpack(
        win32_stream_id_format, data
    )
    print(
        "\nType:",
        stream_type,
        stream_types[stream_type],
        "Attributes:",
        stream_attributes,
        "Size:",
        stream_size,
        "Name len:",
        stream_name_size,
    )
    if stream_name_size > 0:
        ## ??? sdk says this size is in characters, but it appears to be number of bytes ???
        bytes_read, stream_name_buf, ctxt = win32file.BackupRead(
            h, stream_name_size, None, False, True, ctxt
        )
        stream_name = pywintypes.UnicodeFromRaw(stream_name_buf[:])
    else:
        stream_name = "Unnamed"
    print("Name:" + stream_name)
    return (
        ctxt,
        stream_type,
        stream_attributes,
        stream_size,
        stream_name_size,
        stream_name,
    )


ctxt = 0
win32_stream_id_buf = (
    None  ## gets rebound to a writable buffer on first call and reused
)
while 1:
    bytes_read, win32_stream_id_buf, ctxt = win32file.BackupRead(
        h, win32_stream_id_size, win32_stream_id_buf, False, True, ctxt
    )
    if bytes_read == 0:
        break
    (
        ctxt,
        stream_type,
        stream_attributes,
        stream_size,
        stream_name_size,
        stream_name,
    ) = parse_stream_header(h, ctxt, win32_stream_id_buf[:])
    if stream_size > 0:
        bytes_moved = win32file.BackupSeek(h, stream_size, ctxt)
        print("Moved: ", bytes_moved)

win32file.BackupRead(h, win32_stream_id_size, win32_stream_id_buf, True, True, ctxt)
win32file.CloseHandle(h)
