void recurse_dir(MediaScan *s, const char *path, int recurse_count) {
  char *dir, *p;
  char tmp_full_path[MAX_PATH_STR_LEN];
  DIR *dirp;
  struct dirent *dp;
  struct dirq *subdirq;         // list of subdirs of the current directory
  struct dirq_entry *parent_entry = NULL; // entry for current dir in s->_dirq
  char redirect_dir[MAX_PATH_STR_LEN];

  if (recurse_count > RECURSE_LIMIT) {
    LOG_ERROR("Hit recurse limit of %d scanning path %s\n", RECURSE_LIMIT, path);
    return;
  }

  if (path[0] != '/') {         // XXX Win32
    // Get full path
    char *buf = (char *)malloc((size_t)MAX_PATH_STR_LEN);
    if (buf == NULL) {
      FATAL("Out of memory for directory scan\n");
      return;
    }

    dir = getcwd(buf, (size_t)MAX_PATH_STR_LEN);
    strcat(dir, "/");
    strcat(dir, path);
  }
  else {
#ifdef USING_TCMALLOC
    // strdup will cause tcmalloc to crash on free
    dir = (char *)malloc((size_t)MAX_PATH_STR_LEN);
    strcpy(dir, path);
#else
    dir = strdup(path);
#endif
  }

  // Strip trailing slash if any
  p = &dir[0];
  while (*p != 0) {
    if (p[1] == 0 && *p == '/')
      *p = 0;
    p++;
  }

  LOG_INFO("Recursed into %s\n", dir);

#if defined(__APPLE__)
  if (isAlias(dir)) {
    if (CheckMacAlias(dir, redirect_dir)) {
      LOG_INFO("Resolving Alias %s to %s\n", dir, redirect_dir);
      strcpy(dir, redirect_dir);
    }
    else {
      LOG_ERROR("Failure to follow symlink or alias, skipping directory\n");
      goto out;
    }
  }
#elif defined(__linux__)
  if (isAlias(dir)) {
    FollowLink(dir, redirect_dir);
    LOG_INFO("Resolving symlink %s to %s\n", dir, redirect_dir);
    strcpy(dir, redirect_dir);
  }
#endif

  if ((dirp = opendir(dir)) == NULL) {
    LOG_ERROR("Unable to open directory %s: %s\n", dir, strerror(errno));
    goto out;
  }

  subdirq = malloc(sizeof(struct dirq));
  SIMPLEQ_INIT(subdirq);

  while ((dp = readdir(dirp)) != NULL) {
    char *name = dp->d_name;

    // skip all dot files
    if (name[0] != '.') {
      // Check if scan should be aborted
      if (unlikely(s->_want_abort))
        break;

      // XXX some platforms may be missing d_type/DT_DIR
      if (dp->d_type == DT_DIR) {
        // Add to list of subdirectories we need to recurse into
        struct dirq_entry *subdir_entry = malloc(sizeof(struct dirq_entry));

        // Construct full path
        //*tmp_full_path = 0;
        strcpy(tmp_full_path, dir);
        strcat(tmp_full_path, "/");
        strcat(tmp_full_path, name);

        if (_should_scan_dir(s, tmp_full_path)) {
          subdir_entry->dir = strdup(tmp_full_path);
          SIMPLEQ_INSERT_TAIL(subdirq, subdir_entry, entries);

          LOG_INFO(" subdir: %s\n", tmp_full_path);
        }
        else {
          LOG_INFO(" skipping subdir: %s\n", tmp_full_path);
        }
      }
      else {
        enum media_type type = _should_scan(s, name);

        LOG_INFO("name %s = type %d\n", name, type);

        if (type) {
          struct fileq_entry *entry;

          // Check if this file is a shortcut and if so resolve it
#if defined(__APPLE__)
          if (isAlias(name)) {
            char full_name[MAX_PATH_STR_LEN];

            LOG_INFO("Mac Alias detected\n");

            strcpy(full_name, dir);
            strcat(full_name, "\\");
            strcat(full_name, name);
            parse_lnk(full_name, redirect_dir, MAX_PATH_STR_LEN);
            if (PathIsDirectory(redirect_dir)) {
              struct dirq_entry *subdir_entry = malloc(sizeof(struct dirq_entry));

              subdir_entry->dir = strdup(redirect_dir);
              SIMPLEQ_INSERT_TAIL(subdirq, subdir_entry, entries);

              LOG_INFO(" subdir: %s\n", tmp_full_path);
              type = 0;
            }

          }
#elif defined(__linux__)
          if (isAlias(name)) {
            char full_name[MAX_PATH_STR_LEN];

            printf("Linux Alias detected\n");

            strcpy(full_name, dir);
            strcat(full_name, "\\");
            strcat(full_name, name);
            FollowLink(full_name, redirect_dir);
            if (PathIsDirectory(redirect_dir)) {
              struct dirq_entry *subdir_entry = malloc(sizeof(struct dirq_entry));

              subdir_entry->dir = strdup(redirect_dir);
              SIMPLEQ_INSERT_TAIL(subdirq, subdir_entry, entries);

              LOG_INFO(" subdir: %s\n", tmp_full_path);
              type = 0;
            }

          }
#endif
          if (parent_entry == NULL) {
            // Add parent directory to list of dirs with files
            parent_entry = malloc(sizeof(struct dirq_entry));
            parent_entry->dir = strdup(dir);
            parent_entry->files = malloc(sizeof(struct fileq));
            SIMPLEQ_INIT(parent_entry->files);
            SIMPLEQ_INSERT_TAIL((struct dirq *)s->_dirq, parent_entry, entries);
          }

          // Add scannable file to this directory list
          entry = malloc(sizeof(struct fileq_entry));
          entry->file = strdup(name);
          entry->type = type;
          SIMPLEQ_INSERT_TAIL(parent_entry->files, entry, entries);

          s->progress->total++;

          LOG_INFO(" [%5d] file: %s\n", s->progress->total, entry->file);
        }
      }
    }
  }

  closedir(dirp);

  // Send progress update
  if (s->on_progress && !s->_want_abort)
    if (progress_update(s->progress, dir))
      send_progress(s);

  // process subdirs
  while (!SIMPLEQ_EMPTY(subdirq)) {
    struct dirq_entry *subdir_entry = SIMPLEQ_FIRST(subdirq);
    SIMPLEQ_REMOVE_HEAD(subdirq, entries);
    if (!s->_want_abort)
      recurse_dir(s, subdir_entry->dir, recurse_count);
    free(subdir_entry);
  }

  free(subdirq);

out:
  free(dir);
}
Example #2
0
///-------------------------------------------------------------------------------------------------
///  Scan a single file. Everything that applies to ms_scan also applies to this function. If
///   you know the type of the file, set the type paramter to one of TYPE_AUDIO, TYPE_VIDEO, or
///   TYPE_IMAGE. Set it to TYPE_UNKNOWN to have it determined automatically.
///
/// @author Andy Grundman
/// @date 03/15/2011
///
/// @param [in,out] s If non-null, the.
/// @param full_path  Full pathname of the full file.
///
/// ### remarks .
///-------------------------------------------------------------------------------------------------
void ms_scan_file(MediaScan *s, const char *full_path, enum media_type type) {
  MediaScanError *e = NULL;
  MediaScanResult *r = NULL;
  int ret;
  uint32_t hash;
  int mtime = 0;
  uint64_t size = 0;
  DBT key, data;
  char tmp_full_path[MAX_PATH_STR_LEN];

#ifdef WIN32
  char *ext = strrchr(full_path, '.');
#endif

  if (s == NULL) {
    ms_errno = MSENO_NULLSCANOBJ;
    LOG_ERROR("MediaScan = NULL, aborting scan\n");
    return;
  }

  if (s->on_result == NULL) {
    ms_errno = MSENO_NORESULTCALLBACK;
    LOG_ERROR("Result callback not set, aborting scan\n");
    return;
  }

#if (defined(__APPLE__) && defined(__MACH__))
  if (isAlias(full_path)) {
    LOG_INFO("File  %s is a mac alias\n", full_path);
    // Check if this file is a shortcut and if so resolve it
    if (!CheckMacAlias(full_path, tmp_full_path)) {
      LOG_ERROR("Failure to follow symlink or alias, skipping file\n");
      return;
    }
  }
  else {
    strcpy(tmp_full_path, full_path);
  }
#elif defined(__unix__) || defined(__unix)
  if (isAlias(full_path)) {
    LOG_INFO("File %s is a unix symlink\n", full_path);
    // Check if this file is a shortcut and if so resolve it
    FollowLink(full_path, tmp_full_path);
  }
  else {
    strcpy(tmp_full_path, full_path);
  }
#elif defined(WIN32)
  if (strcasecmp(ext, ".lnk") == 0) {
    // Check if this file is a shortcut and if so resolve it
    parse_lnk(full_path, tmp_full_path, MAX_PATH_STR_LEN);
    if (PathIsDirectory(tmp_full_path))
      return;
  }
  else {
    strcpy(tmp_full_path, full_path);
  }
#endif

  // Check if the file has been recently scanned
  hash = HashFile(tmp_full_path, &mtime, &size);

  // Skip 0-byte files
  if (unlikely(size == 0)) {
    LOG_WARN("Skipping 0-byte file: %s\n", tmp_full_path);
    return;
  }

  // Setup DBT values
  memset(&key, 0, sizeof(DBT));
  memset(&data, 0, sizeof(DBT));
  key.data = (char *)full_path;
  key.size = strlen(full_path) + 1;
  data.data = &hash;
  data.size = sizeof(uint32_t);

  if ((s->flags & MS_RESCAN) || (s->flags & MS_FULL_SCAN)) {
    // s->dbp will be null if this function is called directly, if not check if this file is
    // already scanned.
    if (s->dbp != NULL) {
      // DB_GET_BOTH will only return OK if both key and data match, this avoids the need to check
      // the returned data against hash
      int ret = s->dbp->get(s->dbp, NULL, &key, &data, DB_GET_BOTH);
      if (ret != DB_NOTFOUND) {
        //  LOG_INFO("File %s already scanned, skipping\n", tmp_full_path);
        return;
      }
    }
  }

  LOG_INFO("Scanning file %s\n", tmp_full_path);

  if (type == TYPE_UNKNOWN || type == TYPE_LNK) {
    // auto-detect type
    type = _should_scan(s, tmp_full_path);
    if (!type) {
      if (s->on_error) {
        ms_errno = MSENO_SCANERROR;
        e = error_create(tmp_full_path, MS_ERROR_TYPE_UNKNOWN, "Unrecognized file extension");
        send_error(s, e);
        return;
      }
    }
  }

  r = result_create(s);
  if (r == NULL)
    return;

  r->type = type;
  r->path = strdup(full_path);

  if (result_scan(r)) {
    // These were determined by HashFile
    r->mtime = mtime;
    r->size = size;
    r->hash = hash;

    // Store path -> hash data in cache
    if (s->dbp != NULL) {
      memset(&data, 0, sizeof(DBT));
      data.data = &hash;
      data.size = sizeof(uint32_t);

      ret = s->dbp->put(s->dbp, NULL, &key, &data, 0);
      if (ret != 0) {
        s->dbp->err(s->dbp, ret, "Cache store failed: %s", db_strerror(ret));
      }
    }
    send_result(s, r);
  }
  else {
    if (s->on_error && r->error) {
      // Copy the error, because the original will be cleaned up by result_destroy below
      MediaScanError *ecopy = error_copy(r->error);
      send_error(s, ecopy);
    }

    result_destroy(r);
  }
}                               /* ms_scan_file() */