void
scan_itunes_itml(const char *file, time_t mtime, int dir_id)
{
  struct playlist_info *pli;
  struct stat sb;
  char buf[PATH_MAX];
  char *itml_xml;
  plist_t itml;
  plist_t node;
  int fd;
  int ret;

  // This is special playlist that is disabled and only used for saving a timestamp
  pli = db_pl_fetch_bytitlepath(file, file);
  if (pli)
    {
      // mtime == db_timestamp is also treated as a modification because some editors do
      // stuff like 1) close the file with no changes (leading us to update db_timestamp),
      // 2) copy over a modified version from a tmp file (which may result in a mtime that
      // is equal to the newly updated db_timestamp)
      if (mtime && (pli->db_timestamp > mtime))
	{
	  DPRINTF(E_LOG, L_SCAN, "Unchanged iTunes XML found, not processing '%s'\n", file);

	  // TODO Protect the radio stations from purge after scan
	  db_pl_ping_bymatch(file, 0);
	  free_pli(pli, 0);
	  return;
	}

      DPRINTF(E_LOG, L_SCAN, "Modified iTunes XML found, processing '%s'\n", file);

      // Clear out everything, we will recreate
      db_pl_delete_bypath(file);
      free_pli(pli, 0);
    }
  else
    {
      DPRINTF(E_LOG, L_SCAN, "New iTunes XML found, processing: '%s'\n", file);
    }

  CHECK_NULL(L_SCAN, pli = calloc(1, sizeof(struct playlist_info)));

  pli->type = PL_PLAIN;
  pli->title = strdup(file);
  pli->path = strdup(file);
  snprintf(buf, sizeof(buf), "/file:%s", file);
  pli->virtual_path = strip_extension(buf);
  pli->directory_id = dir_id;

  ret = db_pl_add(pli, (int *)&pli->id);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_SCAN, "Error adding iTunes XML meta playlist '%s'\n", file);

      free_pli(pli, 0);
      return;
    }

  // Disable, only used for saving timestamp
  db_pl_disable_bypath(file, STRIP_NONE, 0);

  free_pli(pli, 0);

  fd = open(file, O_RDONLY);
  if (fd < 0)
    {
      DPRINTF(E_LOG, L_SCAN, "Could not open iTunes library '%s': %s\n", file, strerror(errno));

      return;
    }

  ret = fstat(fd, &sb);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_SCAN, "Could not stat iTunes library '%s': %s\n", file, strerror(errno));

      close(fd);
      return;
    }

  itml_xml = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
  if (itml_xml == MAP_FAILED)
    {
      DPRINTF(E_LOG, L_SCAN, "Could not map iTunes library '%s': %s\n", file, strerror(errno));

      close(fd);
      return;
    }

  itml = NULL;
  plist_from_xml(itml_xml, sb.st_size, &itml);

  ret = munmap(itml_xml, sb.st_size);
  if (ret < 0)
    DPRINTF(E_LOG, L_SCAN, "Could not unmap iTunes library '%s': %s\n", file, strerror(errno));

  close(fd);

  if (!itml)
    {
      DPRINTF(E_LOG, L_SCAN, "iTunes XML playlist '%s' failed to parse\n", file);

      return;
    }

  if (plist_get_node_type(itml) != PLIST_DICT)
    {
      DPRINTF(E_LOG, L_SCAN, "Malformed iTunes XML playlist '%s'\n", file);

      plist_free(itml);
      return;
    }

  /* Meta data */
  ret = check_meta(itml);
  if (ret < 0)
    {
      plist_free(itml);
      return;
    }

  /* Tracks */
  ret = get_dictval_dict_from_key(itml, "Tracks", &node);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_SCAN, "Could not find Tracks dict in '%s'\n", file);

      plist_free(itml);
      return;
    }

  id_map = calloc(ID_MAP_SIZE, sizeof(struct itml_to_db_map *));
  if (!id_map)
    {
      DPRINTF(E_FATAL, L_SCAN, "iTunes library parser could not allocate ID map\n");

      plist_free(itml);
      return;
    }

  ret = process_tracks(node);
  if (ret <= 0)
    {
      DPRINTF(E_LOG, L_SCAN, "No tracks loaded from iTunes XML '%s'\n", file);

      id_map_free();
      plist_free(itml);
      return;
    }

  DPRINTF(E_LOG, L_SCAN, "Loaded %d tracks from iTunes XML '%s'\n", ret, file);

  /* Playlists */
  ret = get_dictval_array_from_key(itml, "Playlists", &node);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_SCAN, "Could not find Playlists dict in '%s'\n", file);

      id_map_free();
      plist_free(itml);
      return;
    }

  process_pls(node, file);

  id_map_free();
  plist_free(itml);
}
static void
process_pls(plist_t playlists, char *file)
{
  plist_t pl;
  plist_t items;
  struct playlist_info *pli;
  char *name;
  uint64_t id;
  int pl_id;
  uint32_t alen;
  uint32_t i;
  char virtual_path[PATH_MAX];
  int ret;

  alen = plist_array_get_size(playlists);
  for (i = 0; i < alen; i++)
    {
      pl = plist_array_get_item(playlists, i);

      if (plist_get_node_type(pl) != PLIST_DICT)
	continue;

      ret = get_dictval_int_from_key(pl, "Playlist ID", &id);
      if (ret < 0)
	{
	  DPRINTF(E_DBG, L_SCAN, "Playlist ID not found!\n");
	  continue;
	}

      ret = get_dictval_string_from_key(pl, "Name", &name);
      if (ret < 0)
	{
	  DPRINTF(E_DBG, L_SCAN, "Name not found!\n");
	  continue;
	}

      if (ignore_pl(pl, name))
	{
	  free(name);
	  continue;
	}

      pli = db_pl_fetch_bytitlepath(name, file);

      if (pli)
	{
	  pl_id = pli->id;

	  free_pli(pli, 0);

	  db_pl_ping(pl_id);
	  db_pl_clear_items(pl_id);
	}
      else
	pl_id = 0;

      ret = get_dictval_array_from_key(pl, "Playlist Items", &items);
      if (ret < 0)
	{
	  DPRINTF(E_INFO, L_SCAN, "Playlist '%s' has no items\n", name);

	  free(name);
	  continue;
	}

      if (pl_id == 0)
	{
	  snprintf(virtual_path, PATH_MAX, "/file:%s", file);
	  ret = db_pl_add(name, file, virtual_path, &pl_id);
	  if (ret < 0)
	    {
	      DPRINTF(E_LOG, L_SCAN, "Error adding iTunes playlist '%s' (%s)\n", name, file);

	      free(name);
	      continue;
	    }

	  DPRINTF(E_INFO, L_SCAN, "Added playlist as id %d\n", pl_id);
	}

      free(name);

      process_pl_items(items, pl_id);
    }
}