diff --git a/beamtime/nappFileList.m b/beamtime/nappFileList.m index da85f03..e1edaf6 100755 --- a/beamtime/nappFileList.m +++ b/beamtime/nappFileList.m @@ -448,6 +448,10 @@ else % Loop through each file in the NEXAFS file list % TmpFileList{2} contains the full paths to all NEXAFS HDF5 files % ii is the loop counter (1, 2, 3, ... up to the total number of files) + + % Initialize a map to hold the original dates for split files + splitFileDatesMap = containers.Map('KeyType', 'char', 'ValueType', 'any'); + for ii = 1:size(TmpFileList{2}, 1) % Get the filename of the current file being processed @@ -456,6 +460,7 @@ else % Read information about the structure of the HDF5 file % This tells us what groups (datasets) are stored in the file fileinfo = h5info(current_file); + % Extract the names of all groups at the root level % Example: {'/scan_1', '/scan_2', '/logs', '/__DATA_TYPES__'} @@ -465,13 +470,21 @@ else % Files with more than 3 groups likely contain multiple scans % (the extra groups beyond scans are typically '/logs' and '/__DATA_TYPES__') if numel(groupnames) > 3 + % 1. Get the true historical dates BEFORE splitting + [orig_cDateOS, orig_mDateOS] = getOSDates(current_file); + % 2. Split the file and get the names of the newly created files % This file contains multiple scans, so split it % The splitH5Scans function will: % - Create separate files for each scan % - Rename each scan to '/scan_1' within its new file % - Delete the original multi-scan file when done - splitH5Scans(current_file); + newlyCreatedFiles = splitH5Scans(current_file); + + % 3. Store the historical dates in the map using the new filenames as keys + for k = 1:length(newlyCreatedFiles) + splitFileDatesMap(newlyCreatedFiles{k}) = {orig_cDateOS, orig_mDateOS}; + end end @@ -571,9 +584,7 @@ if ~isempty(TmpFileList{1}) dateStr = [char(regexp(tmp{29},'((?<=Date=).*)','match')) ' ' char(regexp(tmp{30},'((?<=Time=).*)','match'))]; parsedDate = datevec(dateStr); if size(parsedDate, 2) == 6 - current_file_info.creationDate = parsedDate; - current_file_info.lastModifiedDatenum = datenum(parsedDate); - current_file_info.lastModifiedDatestr = dateStr; + current_file_info.creationDate = parsedDate; end catch end @@ -654,9 +665,9 @@ if ~isempty(TmpFileList{1}) % If the file lacks "Monochromator" in the header and the energy is % incorrectly tagged as ~1453.6 or 1486.6, overwrite it to 1253.6 (Mg Ka). % ========================================================================= - if ~contains(headerText, 'Monochromator', 'IgnoreCase', true) + if ~any(contains(string(fileLines), 'Monochromator', 'IgnoreCase', true)) % Checking both 1453.6 (as requested) and 1486.61 (as seen in your file) - if (abs(globalExEnergy - 1453.6) < 1) || (abs(globalExEnergy - 1486.61) < 1) + if (abs(globalExEnergy - 1453.61) < 1) || (abs(globalExEnergy - 1486.61) < 1) globalExEnergy = 1253.6; end end @@ -701,6 +712,15 @@ if ~isempty(TmpFileList{1}) rName = strtrim(extractAfter(lineStr, '# Region:')); elseif startsWith(lineStr, '# Excitation Energy:') regionEntry.xRayEkin_eV = sscanf(strtrim(extractAfter(lineStr, '# Excitation Energy:')), '%f'); + % ========================================================================= + % OFFLINE XPS BUG FIX (Mg vs Al Source) - REGION OVERRIDE + % ========================================================================= + if ~any(contains(string(fileLines), 'Monochromator', 'IgnoreCase', true)) + if (abs(regionEntry.xRayEkin_eV - 1453.6) < 1) || (abs(regionEntry.xRayEkin_eV - 1486.61) < 1) + regionEntry.xRayEkin_eV = 1253.6; + end + end + % ========================================================================= exEnergyStr = num2str(regionEntry.xRayEkin_eV); elseif startsWith(lineStr, '# Spectrum ID:') specID = sscanf(strtrim(extractAfter(lineStr, '# Spectrum ID:')), '%f'); @@ -710,10 +730,10 @@ if ~isempty(TmpFileList{1}) try dtUTC = datetime(dateStr, 'InputFormat', 'MM/dd/yy HH:mm:ss', 'PivotYear', 2000, 'TimeZone', 'UTC'); dtLocal = datetime(dtUTC, 'TimeZone', 'Europe/Zurich'); - regionEntry.lastModifiedDatenum = datenum(dtLocal); - regionEntry.lastModifiedDatestr = datestr(dtLocal); + % ONLY set the Start Time (creationDate). Leave lastModified as OS default! + regionEntry.creationDate = datevec(dtLocal); catch - regionEntry.lastModifiedDatestr = rawDateStr; + % If parsing fails, it safely falls back to the OS creation date end elseif startsWith(lineStr, '# Pass Energy:') regionEntry.scientaPassEnergy_eV = str2double(extractAfter(lineStr, '# Pass Energy:')); @@ -852,8 +872,26 @@ for ii = 1:size(TmpFileList{2},1) % do this for each list element % Get OS Dates tmpCurrentFile = fullfile(TmpFileList{2}(ii).folder, TmpFileList{2}(ii).name); - [cDateOS, ~] = getOSDates(tmpCurrentFile); + % ========================================================== + % IN-MEMORY DATE RESTORATION + % Check if this file was created by the splitter. If yes, + % retrieve its historical dates from the memory map! + % ========================================================== + if exist('splitFileDatesMap', 'var') && isKey(splitFileDatesMap, TmpFileList{2}(ii).name) + savedDates = splitFileDatesMap(TmpFileList{2}(ii).name); + cDateOS = savedDates{1}; + mDateOS = savedDates{2}; + else + % Otherwise, just read the dates directly from the OS + [cDateOS, mDateOS] = getOSDates(tmpCurrentFile); + end + + % START TIME: OS Creation Date (or mapped historical creation) TmpFileList{2}(ii).creationDate = datevec(cDateOS); + % END TIME: OS Modification Date (or mapped historical modification) + TmpFileList{2}(ii).lastModifiedDatenum = datenum(mDateOS); + TmpFileList{2}(ii).lastModifiedDatestr = mDateOS; + % ========================================================== try @@ -1148,10 +1186,14 @@ for ii = 1:size(TmpFileList{3},1) % do this for each picture in the list % Get OS Dates tmpCurrentFile = fullfile(TmpFileList{3}(ii).folder, TmpFileList{3}(ii).name); [cDateOS, ~] = getOSDates(tmpCurrentFile); + + % START TIME: OS Creation Date TmpFileList{3}(ii).creationDate = datevec(cDateOS); + % END TIME: Robust OS Modification Date + TmpFileList{3}(ii).lastModifiedDatenum = TmpFileList{3}(ii).datenum; + TmpFileList{3}(ii).lastModifiedDatestr = TmpFileList{3}(ii).date; end - %% Merge lists into one % @@ -1674,9 +1716,10 @@ end end -function splitH5Scans(infile) +function newFiles = splitH5Scans(infile) % splitH5Scans - Split an HDF5 file containing multiple scans into separate files % +% % This function takes an HDF5 file with multiple scan groups (scan_1, scan_2, etc.) % and creates separate files for each scan. Each output file contains only one % scan (renamed to scan_1) plus the logs group. The original file is deleted after @@ -1708,13 +1751,11 @@ function splitH5Scans(infile) % Read the structure of the HDF5 file to see what groups it contains % h5info returns information about groups, datasets, and attributes +newFiles = {}; % Initialize the output cell array fileinfo = h5info(infile); -% Get the original file's modification date before we start -% This will be copied to all new files to preserve the original timestamp -orig_file_info = dir(infile); -orig_mod_datetime = orig_file_info.date; % Get as datetime object - +% Get the original file's true OS dates BEFORE we start modifying it! +[orig_cDateOS, orig_mDateOS] = getOSDates(infile); % Extract the names of all groups at the root level of the file % Example: {'/scan_1', '/scan_2', '/logs', '/__DATA_TYPES__'} @@ -1766,6 +1807,10 @@ if numel(groupnames) > 3 % Example: 'C:\Data\0002_20251119_145823_O_Kedge_Nov25_scan_2.h5' outname = fullfile(filepath, sprintf('%s%s_%s%s', prefix, rest, scan_name_clean, ext)); + % Save the new filename to the output array + [~, base_out, ext_out] = fileparts(outname); + newFiles{end+1} = [base_out, ext_out]; + % Copy the entire original file to create the new output file % We'll modify this copy to keep only one scan copyfile(infile, outname); @@ -1804,13 +1849,32 @@ if numel(groupnames) > 3 % Close the HDF5 file to save all changes H5F.close(fid); - % Copy the modification date from the original file to the new file - % Convert MATLAB datenum to timestamp format: YYYYMMDDhhmm.ss - timestamp_str = string(datetime(orig_mod_datetime), 'yyyyMMddHHmm.ss'); - if ismac - touch_cmd = sprintf('touch -t %s "%s"', timestamp_str, outname); - system(touch_cmd); + % ========================================================== + % RESTORE ORIGINAL OS DATES TO THE NEW FILE + % ========================================================== + if ispc + % Windows: Use .NET to set BOTH Creation and Modification time + try + fileObj = System.IO.FileInfo(outname); + fileObj.CreationTime = System.DateTime.Parse(orig_cDateOS); + fileObj.LastWriteTime = System.DateTime.Parse(orig_mDateOS); + catch + end + + elseif ismac || isunix + % Mac/Linux: Use 'touch' to restore Modification time + try + timestamp_str = string(datetime(orig_mDateOS), 'yyyyMMddHHmm.ss'); + system(sprintf('touch -m -t %s "%s"', timestamp_str, outname)); + catch + end + % NOTE: Mac OS makes it incredibly difficult to change the Birth (Creation) + % time via standard commands. However, because we restored the Modification + % time perfectly, our "Paradox Check" in the main loop will automatically + % step in, realize Creation is newer than Modification, and fix the + % Creation date for us in the data table! end + % ========================================================== % Print confirmation message showing which file was created % fprintf('Created: %s\n', outname); @@ -1825,7 +1889,9 @@ else % disp('File has three or fewer groups. No processing required.'); end -end +end + + function [creationDateOS, lastModifiedDate] = getOSDates(fullPath) @@ -1849,15 +1915,29 @@ if ispc catch creationDateOS = d.date; % Fallback end + elseif ismac || isunix % macOS/Linux: Use the stat command to get the "Birth" time [status, cmdOut] = system(['stat -f "%SB" -t "%d-%b-%Y %H:%M:%S" "', fullPath, '"']); if status == 0 creationDateOS = strtrim(cmdOut); + + % ========================================================== + % macOS COPIED FILE WORKAROUND + % If copied/downloaded, macOS sets Birth Time to 'today' but + % keeps the old Modification Time. If Creation > Modification, + % use the older Modification time as the true origin! + % ========================================================== + try + if datetime(creationDateOS) > datetime(lastModifiedDate) + creationDateOS = lastModifiedDate; + end + catch + % Silently continue if datetime conversion fails + end + % ========================================================== else creationDateOS = d.date; % Fallback end -else - creationDateOS = d.date; % Fallback for unknown OS end end \ No newline at end of file