diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d7f23872..86552e10 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -87,7 +87,7 @@ jobs: sudo apt-get install gcc-mingw-w64 binutils-mingw-w64 mingw-w64-tools - name: Install libusb env: - LIBUSB_VERSION: 1.0.26 + LIBUSB_VERSION: 1.0.29 run: | wget -c https://github.com/libusb/libusb/archive/refs/tags/v${LIBUSB_VERSION}.tar.gz tar xzf v${LIBUSB_VERSION}.tar.gz @@ -99,13 +99,13 @@ jobs: popd - name: Install hidapi env: - HIDAPI_VERSION: 0.12.0 + HIDAPI_VERSION: 0.15.0 run: | wget -c https://github.com/libusb/hidapi/archive/refs/tags/hidapi-${HIDAPI_VERSION}.tar.gz tar xzf hidapi-${HIDAPI_VERSION}.tar.gz pushd hidapi-hidapi-${HIDAPI_VERSION} autoreconf --install --force - ./configure --host=${{ matrix.arch }}-w64-mingw32 LDFLAGS='-static-libgcc' + ./configure --host=${{ matrix.arch }}-w64-mingw32 make make install DESTDIR=$PWD/../artifacts popd @@ -127,36 +127,36 @@ jobs: name: ${{ github.job }}-${{ matrix.arch }} path: ${{ github.job }}-${{ matrix.arch }}.tar.gz -# msvc: -# -# name: Visual Studio -# -# runs-on: windows-latest -# -# strategy: -# fail-fast: false -# matrix: -# platform: [x86, x64] -# -# env: -# CONFIGURATION: Release -# -# steps: -# - uses: actions/checkout@v4 -# - uses: msys2/setup-msys2@v2 -# with: -# install: autoconf automake libtool pkg-config make gcc -# - run: | -# autoreconf --install --force -# ./configure -# make -C src revision.h -# shell: msys2 {0} -# - uses: microsoft/setup-msbuild@v2 -# - run: msbuild -m -p:Platform=${{ matrix.platform }} -p:Configuration=${{ env.CONFIGURATION }} contrib/msvc/libdivecomputer.vcxproj -# - uses: actions/upload-artifact@v4 -# with: -# name: ${{ github.job }}-${{ matrix.platform }} -# path: contrib/msvc/${{ matrix.platform }}/${{ env.CONFIGURATION }}/bin + msvc: + + name: Visual Studio + + runs-on: windows-latest + + strategy: + fail-fast: false + matrix: + platform: [x86, x64] + + env: + CONFIGURATION: Release + + steps: + - uses: actions/checkout@v4 + - uses: msys2/setup-msys2@v2 + with: + install: autoconf automake libtool pkg-config make gcc + - run: | + autoreconf --install --force + ./configure + make -C src revision.h + shell: msys2 {0} + - uses: microsoft/setup-msbuild@v2 + - run: msbuild -m -p:Platform=${{ matrix.platform }} -p:Configuration=${{ env.CONFIGURATION }} contrib/msvc/libdivecomputer.vcxproj + - uses: actions/upload-artifact@v4 + with: + name: ${{ github.job }}-${{ matrix.platform }} + path: contrib/msvc/${{ matrix.platform }}/${{ env.CONFIGURATION }}/bin android: diff --git a/contrib/msvc/libdivecomputer.vcxproj b/contrib/msvc/libdivecomputer.vcxproj index db583a37..25b1aa4b 100644 --- a/contrib/msvc/libdivecomputer.vcxproj +++ b/contrib/msvc/libdivecomputer.vcxproj @@ -207,6 +207,9 @@ + + + @@ -280,6 +283,7 @@ + @@ -340,6 +344,8 @@ + + diff --git a/src/field-cache.c b/src/field-cache.c index 7c16ae49..e3236701 100644 --- a/src/field-cache.c +++ b/src/field-cache.c @@ -2,6 +2,7 @@ #include #include +#include "platform.h" #include "parser-private.h" #include "field-cache.h" diff --git a/src/garmin.c b/src/garmin.c index a3db4058..9232edcb 100644 --- a/src/garmin.c +++ b/src/garmin.c @@ -22,14 +22,39 @@ #include #include #include -#include -#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#include +#include +#include +#ifndef PATH_MAX +#define PATH_MAX MAX_PATH +#endif +#define dc_open _open +#define dc_read _read +#define dc_close _close +#define DC_O_RDONLY _O_RDONLY +#define DC_O_BINARY _O_BINARY +#else #include #include #include #include #include +#define dc_open open +#define dc_read read +#define dc_close close +#define DC_O_RDONLY O_RDONLY +#ifdef O_BINARY +#define DC_O_BINARY O_BINARY +#else +#define DC_O_BINARY 0 +#endif +#endif +#include "platform.h" #include "garmin.h" #include "context-private.h" #include "device-private.h" @@ -275,6 +300,56 @@ add_name(struct file_list *files, const char *name, unsigned int mtp_id) entry->mtp_id = mtp_id; } +#ifdef _WIN32 +static dc_status_t +get_file_list(dc_device_t *abstract, const char *pathname, struct file_list *files) +{ + WIN32_FIND_DATAA findData; + HANDLE hFind; + char searchPath[PATH_MAX]; + + DEBUG(abstract->context, "Iterating over Garmin files"); + + // Create search pattern (pathname\*.fit) + snprintf(searchPath, sizeof(searchPath), "%s\\*", pathname); + + hFind = FindFirstFileA(searchPath, &findData); + if (hFind == INVALID_HANDLE_VALUE) { + /* Directory does not exist; caller may retry with a fallback path. + * Nothing has been added to 'files' yet, so no cleanup needed. */ + return DC_STATUS_NODEVICE; + } + + do { + // Skip directories + if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + continue; + + if (!check_filename(abstract, findData.cFileName)) + continue; + + dc_status_t rc = make_space(files); + if (rc != DC_STATUS_SUCCESS) { + FindClose(hFind); + return rc; + } + add_name(files, findData.cFileName, 0); + } while (FindNextFileA(hFind, &findData)); + + if (GetLastError() != ERROR_NO_MORE_FILES) { + FindClose(hFind); + return DC_STATUS_IO; + } + + FindClose(hFind); + + DEBUG(abstract->context, "Found %d files", files->nr); + + if (files->array) + qsort(files->array, files->nr, sizeof(struct fit_file), name_cmp); + return DC_STATUS_SUCCESS; +} +#else static dc_status_t get_file_list(dc_device_t *abstract, DIR *dir, struct file_list *files) { @@ -296,6 +371,7 @@ get_file_list(dc_device_t *abstract, DIR *dir, struct file_list *files) qsort(files->array, files->nr, sizeof(struct fit_file), name_cmp); return DC_STATUS_SUCCESS; } +#endif #ifdef HAVE_LIBMTP static unsigned int @@ -438,10 +514,6 @@ mtp_read_file(garmin_device_t *device, unsigned int file_id, dc_buffer_t *file) } #endif /* HAVE_LIBMTP */ -#ifndef O_BINARY -#define O_BINARY 0 -#endif - static dc_status_t read_file(char *pathname, int pathlen, const char *name, dc_buffer_t *file) { @@ -449,7 +521,7 @@ read_file(char *pathname, int pathlen, const char *name, dc_buffer_t *file) pathname[pathlen] = '/'; memcpy(pathname+pathlen+1, name, FILE_NAME_SIZE); - fd = open(pathname, O_RDONLY | O_BINARY); + fd = dc_open(pathname, DC_O_RDONLY | DC_O_BINARY); if (fd < 0) return DC_STATUS_IO; @@ -459,7 +531,7 @@ read_file(char *pathname, int pathlen, const char *name, dc_buffer_t *file) char buffer[4096]; int n; - n = read(fd, buffer, sizeof(buffer)); + n = dc_read(fd, buffer, sizeof(buffer)); if (!n) break; if (n > 0) { @@ -470,7 +542,7 @@ read_file(char *pathname, int pathlen, const char *name, dc_buffer_t *file) break; } - close(fd); + dc_close(fd); return rc; } @@ -488,7 +560,9 @@ garmin_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void NULL // array of file names / ids }; dc_buffer_t *file; +#ifndef _WIN32 DIR *dir; +#endif dc_status_t rc; // Read the directory name from the iostream @@ -526,6 +600,31 @@ garmin_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void } else #endif { // slight coding style violation to deal with the non-MTP case +#ifdef _WIN32 + // On Windows, get_file_list takes the pathname directly + rc = get_file_list(abstract, pathname, &files); + if (rc == DC_STATUS_NODEVICE) { + /* Garmin/Activity directory not found; 'files' is untouched. + * Try the input path directly as a fallback. */ + rc = get_file_list(abstract, pathname_input, &files); + if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to open directory '%s' or '%s'.", pathname, pathname_input); + free(files.array); + return rc; + } + strcpy(pathname, pathname_input); + pathlen = strlen(pathname); + } else if (rc != DC_STATUS_SUCCESS) { + /* Real error (e.g. DC_STATUS_NOMEMORY or mid-enumeration IO + * error): files may be partially populated, don't retry. */ + free(files.array); + return rc; + } + if (!files.nr) { + free(files.array); + return rc; + } +#else dir = opendir(pathname); if (!dir) { dir = opendir(pathname_input); @@ -543,6 +642,7 @@ garmin_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void free(files.array); return rc; } +#endif } // We found at least one file // Can we find the fingerprint entry? diff --git a/src/garmin_parser.c b/src/garmin_parser.c index a06fb777..0f4764fe 100644 --- a/src/garmin_parser.c +++ b/src/garmin_parser.c @@ -1217,10 +1217,14 @@ static const struct { #define MSG_NAME_LEN 16 static const struct msg_desc *lookup_msg_desc(unsigned short msg, int local, const char **namep) { - static struct msg_desc local_array[16]; + /* + * All unknown/local messages share a single zero-initialised descriptor. + * maxfield == 0 means no fields will ever be indexed via ->field[]. + * Declaring a single named instance of a struct with a flexible array + * member is valid C99; the flexible array simply has zero elements. + */ + static const struct msg_desc zero_desc = { .maxfield = 0 }; static char local_name[16][MSG_NAME_LEN]; - struct msg_desc *desc; - char *name; /* Do we have a real one? */ if (msg < C_ARRAY_SIZE(message_array) && message_array[msg].name) { @@ -1229,13 +1233,9 @@ static const struct msg_desc *lookup_msg_desc(unsigned short msg, int local, con } /* If not, fake it */ - desc = &local_array[local]; - memset(desc, 0, sizeof(*desc)); - - name = local_name[local]; - snprintf(name, MSG_NAME_LEN, "msg-%d", msg); - *namep = name; - return desc; + snprintf(local_name[local], MSG_NAME_LEN, "msg-%d", msg); + *namep = local_name[local]; + return &zero_desc; } static int all_data_inval(const unsigned char *data, int base_type, int len) diff --git a/src/platform.h b/src/platform.h index 5f7ca962..317ba270 100644 --- a/src/platform.h +++ b/src/platform.h @@ -48,6 +48,7 @@ extern "C" { #ifdef _MSC_VER #define strcasecmp _stricmp #define strncasecmp _strnicmp +#define strdup _strdup #if _MSC_VER < 1800 // The rint() function is only available in MSVC 2013 and later // versions. Our replacement macro isn't entirely correct, because the diff --git a/src/usb_storage.c b/src/usb_storage.c index ab33a03b..842531e0 100644 --- a/src/usb_storage.c +++ b/src/usb_storage.c @@ -23,13 +23,31 @@ #include "config.h" #endif +#include +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#include +#include +#include +#ifndef PATH_MAX +#define PATH_MAX MAX_PATH +#endif +#define dc_stat _stat +#define dc_stat_t struct _stat +#define DC_S_ISDIR(mode) (((mode) & _S_IFMT) == _S_IFDIR) +#else #include #include #include #include -#include -#include -#include +#define dc_stat stat +#define dc_stat_t struct stat +#define DC_S_ISDIR(mode) S_ISDIR(mode) +#endif #include "common-private.h" #include "context-private.h" @@ -70,7 +88,7 @@ dc_status_t dc_usb_storage_open (dc_iostream_t **out, dc_context_t *context, const char *name) { dc_usbstorage_t *device = NULL; - struct stat st; + dc_stat_t st; if (out == NULL || name == NULL) return DC_STATUS_INVALIDARGS; @@ -80,7 +98,7 @@ dc_usb_storage_open (dc_iostream_t **out, dc_context_t *context, const char *nam INFO (context, "Open MTP device"); } else { INFO (context, "Open: name=%s", name); - if (stat(name, &st) < 0 || !S_ISDIR(st.st_mode)) + if (dc_stat(name, &st) < 0 || !DC_S_ISDIR(st.st_mode)) return DC_STATUS_NODEVICE; } // Allocate memory.