# HG changeset patch
# User Markus Mützel <markus.muetzel@gmx.de>
# Date 1683828704 -7200
#      Thu May 11 20:11:44 2023 +0200
# Node ID 5b5b116e802676496db937eb377a78fe72234071
# Parent  a4fbcbaa0aa2cc8f9ee2f275b99dbca87876d087
load-path: Avoid using file_stat on Windows (bug #59711).

* liboctave/system/oct-time.h, oct-time.cc (octave::sys::file_time): New class
to handle the native file time type per platform.

* libinterp/corefcn/load-path.h (load_path::dir_info::dir_mtime,
load_path::dir_info::dir_time_last_checked): Change type to new class.

* libinterp/corefcn/load-path.cc (subdirs_modified, load_path::dir_info::update,
load_path::dir_info::initialize): Avoid using file_stat on Windows.

diff -r a4fbcbaa0aa2 -r 5b5b116e8026 libinterp/corefcn/load-path.cc
--- a/libinterp/corefcn/load-path.cc	Sat May 06 18:12:59 2023 +0200
+++ b/libinterp/corefcn/load-path.cc	Thu May 11 20:11:44 2023 +0200
@@ -193,7 +193,7 @@
 //! @return true if directory contains modified subdirectories
 
 static bool
-subdirs_modified (const std::string& d, const sys::time& last_checked)
+subdirs_modified (const std::string& d, const sys::file_time& last_checked)
 {
   sys::dir_entry dir (d);
 
@@ -209,13 +209,22 @@
 
           std::string full_name = sys::file_ops::concat (d, fname);
 
-          sys::file_stat fs (full_name);
-
           // Check if directory AND if relevant (@,+,private)
           // AND (if modified OR recursion into (@,+) sub-directories)
+#if defined (OCTAVE_USE_WINDOWS_API)
+          if (sys::dir_exists (full_name)
+#else
+          sys::file_stat fs (full_name);
+
           if (fs && fs.is_dir ()
+#endif
               && (fname[0] == '@' || fname[0] == '+' || fname == "private")
-              && ((fs.mtime () + fs.time_resolution () > last_checked)
+#if defined (OCTAVE_USE_WINDOWS_API)
+              && ((sys::file_time (full_name)
+#else
+              && ((sys::file_time (fs.mtime ().unix_time ())
+#endif
+                   + sys::file_time::time_resolution () > last_checked)
                   || ((fname[0] == '@' || fname[0] == '+')
                       && subdirs_modified (full_name, last_checked))))
             return true;
@@ -1354,11 +1363,18 @@
 bool
 load_path::dir_info::update ()
 {
+#if defined (OCTAVE_USE_WINDOWS_API)
+  std::string msg;
+
+  if (! sys::dir_exists (dir_name, msg))
+    {
+#else
   sys::file_stat fs (dir_name);
 
   if (! fs)
     {
       std::string msg = fs.error ();
+#endif
       warning_with_id ("Octave:load-path:dir-info:update-failed",
                        "load_path: %s: %s", dir_name.c_str (), msg.c_str ());
 
@@ -1382,7 +1398,12 @@
               // slow things down tremendously for large directories.
               const dir_info& di = p->second;
 
-              if ((fs.mtime () + fs.time_resolution ()
+#if defined (OCTAVE_USE_WINDOWS_API)
+              if ((sys::file_time (dir_name)
+#else
+              if ((sys::file_time (fs.mtime ().unix_time ())
+#endif
+                   + sys::file_time::time_resolution ()
                    > di.dir_time_last_checked)
                   || subdirs_modified (dir_name, dir_time_last_checked))
                 initialize ();
@@ -1417,7 +1438,12 @@
         }
     }
   // Absolute path, check timestamp to see whether it requires re-caching
-  else if (fs.mtime () + fs.time_resolution () > dir_time_last_checked
+#if defined (OCTAVE_USE_WINDOWS_API)
+  else if (sys::file_time (dir_name)
+#else
+  else if (sys::file_time (fs.mtime ().unix_time ())
+#endif
+           + sys::file_time::time_resolution () > dir_time_last_checked
            || subdirs_modified (dir_name, dir_time_last_checked))
     initialize ();
 
@@ -1450,17 +1476,28 @@
 {
   is_relative = ! sys::env::absolute_pathname (dir_name);
 
-  dir_time_last_checked = sys::time (static_cast<OCTAVE_TIME_T> (0));
-
+  dir_time_last_checked = sys::file_time (static_cast<OCTAVE_TIME_T> (0));
+
+#if defined (OCTAVE_USE_WINDOWS_API)
+  std::string msg;
+
+  if (sys::dir_exists (dir_name, msg))
+#else
   sys::file_stat fs (dir_name);
 
   if (fs)
+#endif
     {
       method_file_map.clear ();
       package_dir_map.clear ();
 
-      dir_mtime = fs.mtime ();
-      dir_time_last_checked = sys::time ();
+#if defined (OCTAVE_USE_WINDOWS_API)
+      dir_mtime = sys::file_time (dir_name);
+#else
+      dir_mtime = fs.mtime ().unix_time ();
+#endif
+
+      dir_time_last_checked = sys::file_time ();
 
       get_file_list (dir_name);
 
@@ -1486,7 +1523,9 @@
     }
   else
     {
+#if ! defined (OCTAVE_USE_WINDOWS_API)
       std::string msg = fs.error ();
+#endif
       warning ("load_path: %s: %s", dir_name.c_str (), msg.c_str ());
     }
 }
diff -r a4fbcbaa0aa2 -r 5b5b116e8026 libinterp/corefcn/load-path.h
--- a/libinterp/corefcn/load-path.h	Sat May 06 18:12:59 2023 +0200
+++ b/libinterp/corefcn/load-path.h	Thu May 11 20:11:44 2023 +0200
@@ -289,8 +289,8 @@
     std::string dir_name;
     std::string abs_dir_name;
     bool is_relative;
-    sys::time dir_mtime;
-    sys::time dir_time_last_checked;
+    sys::file_time dir_mtime;
+    sys::file_time dir_time_last_checked;
     string_vector all_files;
     string_vector fcn_files;
     fcn_file_map_type private_file_map;
diff -r a4fbcbaa0aa2 -r 5b5b116e8026 liboctave/system/oct-time.cc
--- a/liboctave/system/oct-time.cc	Sat May 06 18:12:59 2023 +0200
+++ b/liboctave/system/oct-time.cc	Thu May 11 20:11:44 2023 +0200
@@ -370,5 +370,28 @@
   m_cpu = cpu_time (usr_sec, sys_sec, usr_usec, sys_usec);
 }
 
+file_time::file_time (const std::string& filename)
+{
+#if defined (OCTAVE_USE_WINDOWS_API)
+  std::wstring wfull_name = sys::u8_to_wstring (filename);
+  HANDLE h_file
+    = CreateFile (wfull_name.c_str (), GENERIC_READ,
+                  FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0,
+                  nullptr);
+  FILETIME last_access_time;
+  GetFileTime (h_file, nullptr, &last_access_time, nullptr);
+
+  m_time
+    = (static_cast<OCTAVE_TIME_T> (last_access_time.dwHighDateTime)) >> 32
+      | last_access_time.dwLowDateTime;
+#else
+  time_t ot_unix_time;
+  time_t ot_usec;
+  octave_gettimeofday_wrapper (&ot_unix_time, &ot_usec);
+  // Discard usec.  We are assuming a 1 second resolution anyway.
+  m_time = ot_unix_time;
+#endif
+}
+
 OCTAVE_END_NAMESPACE(sys)
 OCTAVE_END_NAMESPACE(octave)
diff -r a4fbcbaa0aa2 -r 5b5b116e8026 liboctave/system/oct-time.h
--- a/liboctave/system/oct-time.h	Sat May 06 18:12:59 2023 +0200
+++ b/liboctave/system/oct-time.h	Thu May 11 20:11:44 2023 +0200
@@ -31,6 +31,11 @@
 #include <iosfwd>
 #include <string>
 
+#if defined (OCTAVE_USE_WINDOWS_API)
+#  include "windows.h"
+#endif
+
+
 static inline double
 as_double (OCTAVE_TIME_T sec, long usec)
 {
@@ -465,6 +470,125 @@
   long m_nivcsw;
 };
 
+// class to handle file time efficiently on different platforms
+
+class file_time
+{
+public:
+
+  file_time ()
+  {
+#if defined (OCTAVE_USE_WINDOWS_API)
+    FILETIME curr_file_time;
+    GetSystemTimeAsFileTime (&curr_file_time);
+    m_time
+      = (static_cast<OCTAVE_TIME_T> (curr_file_time.dwHighDateTime)) >> 32
+        | curr_file_time.dwLowDateTime;
+#else
+    sys::time now;
+    m_time = now.unix_time ();
+#endif
+  }
+
+  file_time (OCTAVE_TIME_T t)
+    : m_time (t)
+  { }
+
+#if defined (OCTAVE_USE_WINDOWS_API)
+  file_time (FILETIME& t)
+  {
+    m_time = (static_cast<OCTAVE_TIME_T> (t.dwHighDateTime)) >> 32
+             | t.dwLowDateTime;
+  }
+#endif
+
+  OCTAVE_API file_time (const std::string& filename);
+
+  file_time (const file_time& ot)
+  {
+    m_time = ot.time ();
+  }
+
+  file_time& operator = (const file_time& ot)
+  {
+    if (this != &ot)
+      m_time = ot.time ();
+
+    return *this;
+  }
+
+  ~file_time () = default;
+
+  inline static file_time time_resolution ()
+  {
+#if defined (OCTAVE_USE_WINDOWS_API)
+    // FAT file systems have 2 seconds resolution for the modification time.
+    static OCTAVE_TIME_T time_resolution = 20000;
+#else
+    // Assume 1 second (see file_stat)
+    static OCTAVE_TIME_T time_resolution = 1;
+#endif
+    return time_resolution;
+  }
+
+  inline bool
+  operator == (const file_time& t2) const
+  {
+    return time () == t2.time ();
+  }
+
+  inline bool
+  operator != (const file_time& t2) const
+  {
+    return ! (*this == t2);
+  }
+
+  inline bool
+  operator < (const file_time& t2) const
+  {
+    return time () < t2.time ();
+  }
+
+  inline bool
+  operator <= (const file_time& t2) const
+  {
+    return (*this < t2 || *this == t2);
+  }
+
+  inline bool
+  operator > (const file_time& t2) const
+  {
+    return time () > t2.time ();
+  }
+
+  inline bool
+  operator >= (const file_time& t2) const
+  {
+    return (*this > t2 || *this == t2);
+  }
+
+  inline file_time
+  operator + (const file_time& t2) const
+  {
+    return file_time (time () + t2.time ());
+  }
+
+  inline file_time
+  operator + (const OCTAVE_TIME_T t2) const
+  {
+    return file_time (time () + t2);
+  }
+
+  OCTAVE_TIME_T time () const { return m_time; }
+
+private:
+
+  // The native file time type differs per platform.
+  // On POSIX, this is the number of 1 second intervals since the epoch.
+  // On Windows, this is the number of 0.1 ms intervals since a different epoch.
+  OCTAVE_TIME_T m_time;
+};
+
 OCTAVE_END_NAMESPACE(sys)
 OCTAVE_END_NAMESPACE(octave)
 
