上一章介绍的是图片预览的主要实现方式,这次来介绍下Thunar实现预览的流程。
./thunar/thunar-icon-factory.c获取文件的图标 thunar_icon_factory_load_file_icon()。
对于已经存在缩略图的查找缩略图thunar_vfs_thumb_factory_lookup_thumbnail(),
判断其修改时间和缩略图修改时间是否一致thunar_vfs_thumbnail_is_valid(),不一致则重新生成 。
如果找不到缩略图,或者需要重新生成缩略图的文件,调用thunar_thumbnail_generator_enqueue()。
最后,生成缩略图后当作文件的图标。
GdkPixbuf* thunar_icon_factory_load_file_icon (ThunarIconFactory *factory, ThunarFile *file, ThunarFileIconState icon_state, gint icon_size) { ThunarFileThumbState thumb_state; ThunarVfsFileTime time; ThunarVfsInfo *info; ThunarVfsPath *path; ThunarIconKey key; const gchar *icon_name; GdkPixbuf *icon; gchar *thumb_path; _thunar_return_val_if_fail (THUNAR_IS_ICON_FACTORY (factory), NULL); _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); _thunar_return_val_if_fail (icon_size > 0, NULL); /* check if there's a custom icon for the file */ icon_name = thunar_file_get_custom_icon (file); if (G_UNLIKELY (icon_name != NULL)) { /* try to load the icon */ icon = thunar_icon_factory_lookup_icon (factory, icon_name, icon_size, FALSE); if (G_LIKELY (icon != NULL)) return icon; } /* check if thumbnails are enabled and we can display a thumbnail for the item */ if (G_LIKELY (factory->show_thumbnails && thunar_file_is_regular (file))) { /* determine the thumbnail state */ thumb_state = thunar_file_get_thumb_state (file); /* check if we haven't yet determine the thumbnail state */ if (thumb_state == THUNAR_FILE_THUMB_STATE_UNKNOWN) { again: /* determine the ThunarVfsInfo for the file */ info = thunar_file_get_info (file); /* try to load an existing thumbnail for the file */ thumb_path = thunar_vfs_thumb_factory_lookup_thumbnail (factory->thumbnail_factory, info); /* check if we can generate a thumbnail in case there's none yet */ if (G_UNLIKELY (thumb_path == NULL && thunar_vfs_thumb_factory_can_thumbnail (factory->thumbnail_factory, info))) { /* schedule the thumbnail loading for the file */ thunar_thumbnail_generator_enqueue (factory->thumbnail_generator, file); /* set the thumbnail state to "loading" */ thumb_state = THUNAR_FILE_THUMB_STATE_LOADING; } if (G_LIKELY (thumb_path != NULL)) { thumb_state = THUNAR_FILE_THUMB_STATE_READY; g_object_set_qdata_full (G_OBJECT (file), thunar_file_thumb_path_quark, thumb_path, g_free); } else if (thumb_state != THUNAR_FILE_THUMB_STATE_LOADING) { thumb_state = THUNAR_FILE_THUMB_STATE_NONE; } /* apply the new state */ thunar_file_set_thumb_state (file, thumb_state); } ... }
./thunar/thunar-thumbnail-generator.c中开辟一个新线程来进行缩略图的生成:
void thunar_thumbnail_generator_enqueue (ThunarThumbnailGenerator *generator, ThunarFile *file) { ThunarThumbnailInfo *info; GError *error = NULL; GList *lp; _thunar_return_if_fail (THUNAR_IS_THUMBNAIL_GENERATOR (generator)); _thunar_return_if_fail (THUNAR_IS_FILE (file)); /* acquire the generator lock */ g_mutex_lock (generator->lock); /* check if the file is already on the request list */ for (lp = generator->requests; lp != NULL; lp = lp->next) if (((ThunarThumbnailInfo *) lp->data)->file == file) break; /* schedule a request for the file if it's not already done */ if (G_LIKELY (lp == NULL)) { /* allocate a thumbnail info for the file */ info = _thunar_slice_new (ThunarThumbnailInfo); info->file = g_object_ref (G_OBJECT (file)); info->info = thunar_vfs_info_copy (thunar_file_get_info (file)); /* schedule the request */ generator->requests = g_list_append (generator->requests, info); /* start the generator thread on-demand */ if (G_UNLIKELY (generator->thread == NULL)) { /* 开辟新线程来进行缩略图生成 */ generator->thread = g_thread_create_full (thunar_thumbnail_generator_thread, generator, 0, FALSE, FALSE, G_THREAD_PRIORITY_LOW, &error); if (G_UNLIKELY (generator->thread == NULL)) { g_warning ("Failed to launch thumbnail generator thread: %s", error->message); g_error_free (error); } } } /* release the generator lock */ g_mutex_unlock (generator->lock); } static gpointer thunar_thumbnail_generator_thread (gpointer user_data) { ThunarThumbnailGenerator *generator = THUNAR_THUMBNAIL_GENERATOR (user_data); ThunarThumbnailInfo *info; GdkPixbuf *icon; for (;;) { /* lock the factory */ g_mutex_lock (generator->lock); /* grab the first thumbnail from the request list */ if (G_LIKELY (generator->requests != NULL)) { /* 获取需要生成缩略图的文件信息 */ info = generator->requests->data; generator->requests = g_list_remove (generator->requests, info); } else { info = NULL; } /* check if we have something to generate */ if (G_UNLIKELY (info == NULL)) { /* reset the thread pointer */ generator->thread = NULL; g_cond_signal (generator->cond); g_mutex_unlock (generator->lock); break; } /* release the lock */ g_mutex_unlock (generator->lock); /* don't generate thumbnails for files for which only we own a reference */ if (G_LIKELY (G_OBJECT (info->file)->ref_count > 1)) { /* try to generate the thumbnail */ /* 调用thumbnail生成函数,生成thumbnail */ icon = thunar_vfs_thumb_factory_generate_thumbnail (generator->factory, info->info); /* store the thumbnail (or the failed notice) */ /* 将生成的thumbnail最终保存到$HOME/.thumbnail中,并同时保存相应的文件创建时间等信息 */ /* 通过比较文件创建的时间,对于同一文件就不用每次都生成文件thumbnail */ thunar_vfs_thumb_factory_store_thumbnail (generator->factory, icon, info->info, NULL); } else { icon = NULL; } /* place the thumbnail on the reply list and schedule the idle source */ g_mutex_lock (generator->lock); generator->replies = g_list_prepend (generator->replies, info); /* 生成好thumbnail后给所有文件发送一个changed信号 */ if (G_UNLIKELY (generator->idle_id < 0)) generator->idle_id = g_idle_add_full (G_PRIORITY_LOW, thunar_thumbnail_generator_idle, generator, NULL); g_mutex_unlock (generator->lock); /* release the icon (if any) */ if (G_LIKELY (icon != NULL)) g_object_unref (G_OBJECT (icon)); } return NULL; }
./thunar-vfs/thunar-vfs-thumb.c具体的缩略图生成实现类:
/** * thunar_vfs_thumb_factory_generate_thumbnail: * @factory : a #ThunarVfsThumbFactory. * @info : the #ThunarVfsInfo of the file for which a thumbnail * should be generated. * * Tries to generate a thumbnail for the file referred to by @info in * @factory. * * The caller is responsible to free the returned #GdkPixbuf using * g_object_unref() when no longer needed. * * The usage of this method is thread-safe. * * Return value: the thumbnail for the @uri or %NULL. **/ GdkPixbuf* thunar_vfs_thumb_factory_generate_thumbnail (ThunarVfsThumbFactory *factory, const ThunarVfsInfo *info) { ... }
/** * thunar_vfs_thumb_factory_store_thumbnail: * @factory : a #ThunarVfsThumbFactory. * @pixbuf : the thumbnail #GdkPixbuf to store or %NULL * to remember the thumbnail for @uri as failed. * @info : the #ThunarVfsInfo of the original file. * @error : return location for errors or %NULL. * * Stores @pixbuf as thumbnail for @info in the right place, according * to the size set for @factory. * * If you specify %NULL for @pixbuf, the @factory will remember that * the thumbnail generation for @info failed. * * The usage of this method is thread-safe. * * Return value: %TRUE if the thumbnail was stored successfully, * else %FALSE. **/ gboolean thunar_vfs_thumb_factory_store_thumbnail (ThunarVfsThumbFactory *factory, const GdkPixbuf *pixbuf, const ThunarVfsInfo *info, GError **error) { const gchar *base_path; GdkPixbuf *thumbnail; gboolean succeed; gchar *dst_path; gchar *tmp_path; gchar *mtime; gchar *size; gchar *md5; gchar *uri; gint tmp_fd; g_return_val_if_fail (THUNAR_VFS_IS_THUMB_FACTORY (factory), FALSE); g_return_val_if_fail (pixbuf == NULL || GDK_IS_PIXBUF (pixbuf), FALSE); g_return_val_if_fail (info != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* check whether we should save a thumbnail or remember failed generation */ base_path = G_LIKELY (pixbuf != NULL) ? factory->base_path : factory->fail_path; /* verify that the target directory exists */ if (!xfce_mkdirhier (base_path, 0700, error)) return FALSE; /* determine the URI of the file */ uri = thunar_vfs_path_dup_uri (info->path); /* determine the MD5 sum for the URI */ md5 = exo_str_get_md5_str (uri); /* try to open a temporary file to write the thumbnail to */ /* 开辟临时文件到$HOME/.thumbnail作为thumbnail的容器 */ tmp_path = g_strconcat (base_path, md5, ".png.XXXXXX", NULL); tmp_fd = g_mkstemp (tmp_path); if (G_UNLIKELY (tmp_fd < 0)) { g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), "%s", g_strerror (errno)); g_free (md5); g_free (uri); return FALSE; } /* close the temporary file as it exists now, and hence * we successfully avoided a race condition there. */ close (tmp_fd); /* generate a 1x1 image if we're storing a failure */ thumbnail = (pixbuf != NULL) ? GDK_PIXBUF (pixbuf) : gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 1, 1); /* determine string representations for the modification time and size */ mtime = g_strdup_printf ("%lu", (gulong) info->mtime); size = g_strdup_printf ("%" G_GUINT64_FORMAT, (guint64) info->size); /* write the thumbnail to the temporary location */ /* 保存thumbnail文件信息 */ succeed = gdk_pixbuf_save (thumbnail, tmp_path, "png", error, "tEXt::Thumb::URI", uri, "tEXt::Thumb::Size", size, /* 保存源文件的修改时间到thumbnail */ "tEXt::Thumb::MTime", mtime, /* 保存源文件的文件类型到thumbnail */ "tEXt::Thumb::Mimetype", thunar_vfs_mime_info_get_name (info->mime_info), "tEXt::Software", "Thunar-VFS Thumbnail Factory", NULL); /* drop the failed thumbnail pixbuf (if any) */ if (G_UNLIKELY (pixbuf == NULL)) g_object_unref (G_OBJECT (thumbnail)); /* rename the file to the final location */ if (G_LIKELY (succeed)) { dst_path = g_strconcat (base_path, md5, ".png", NULL); if (G_UNLIKELY (g_rename (tmp_path, dst_path) < 0)) { g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), "%s", g_strerror (errno)); succeed = FALSE; } g_free (dst_path); } /* cleanup */ g_free (tmp_path); g_free (mtime); g_free (size); g_free (md5); g_free (uri); return succeed; }
/** * thunar_vfs_thumb_factory_lookup: * @factory : a #ThunarVfsThumbFactory. * @info : the #ThunarVfsInfo of the original file. * * Looks up the path to a thumbnail for @info in @factory and returns * the absolute path to the thumbnail file if a valid thumbnail is * found. Else %NULL is returned. * * The usage of this method is thread-safe. * * Return value: the path to a valid thumbnail for @info in @factory * or %NULL if no valid thumbnail was found. **/ gchar* thunar_vfs_thumb_factory_lookup_thumbnail (ThunarVfsThumbFactory *factory, const ThunarVfsInfo *info) { gchar uri[THUNAR_VFS_PATH_MAXURILEN]; gchar *path; gchar *md5; g_return_val_if_fail (THUNAR_VFS_IS_THUMB_FACTORY (factory), NULL); g_return_val_if_fail (info != NULL, NULL); /* determine the URI for the path */ if (thunar_vfs_path_to_uri (info->path, uri, sizeof (uri), NULL) >= 0) { /* determine the path to the thumbnail for the factory */ md5 = exo_str_get_md5_str (uri); path = g_strconcat (factory->base_path, md5, ".png", NULL); g_free (md5); /* check if the path contains a valid thumbnail */ /* 判断在$HOME/.thumbnail是否有thumbnail */ /* 以及thumbnail是否需要重新生成 */ if (thunar_vfs_thumbnail_is_valid (path, uri, info->mtime)) return path; /* no valid thumbnail in the global repository */ g_free (path); } return NULL; }
/** * thunar_vfs_thumbnail_is_valid: * @thumbnail : the absolute path to a thumbnail file. * @uri : the URI of the original file. * @mtime : the modification time of the original file. * * Checks whether the file located at @thumbnail contains a * valid thumbnail for the file described by @uri and @mtime. * * The usage of this method is thread-safe. * * Return value: %TRUE if @thumbnail is a valid thumbnail for * the file referred to by @uri, else %FALSE. **/ gboolean thunar_vfs_thumbnail_is_valid (const gchar *thumbnail, const gchar *uri, ThunarVfsFileTime mtime) { png_structp png_ptr; png_infop info_ptr; png_textp text_ptr; gboolean is_valid = FALSE; gchar signature[4]; FILE *fp; gint n_checked; gint n_text; gint n; g_return_val_if_fail (g_path_is_absolute (thumbnail), FALSE); g_return_val_if_fail (uri != NULL && *uri != '\0', FALSE); /* try to open the thumbnail file */ fp = fopen (thumbnail, "r"); if (G_UNLIKELY (fp == NULL)) return FALSE; /* read the png signature */ if (G_UNLIKELY (fread (signature, 1, sizeof (signature), fp) != sizeof (signature))) goto done0; /* verify the png signature */ if (G_LIKELY (png_check_sig ((png_bytep) signature, sizeof (signature)))) rewind (fp); else goto done0; /* allocate the png read structure */ png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (G_UNLIKELY (png_ptr == NULL)) goto done0; /* allocate the png info structure */ info_ptr = png_create_info_struct (png_ptr); if (G_UNLIKELY (info_ptr == NULL)) { png_destroy_read_struct (&png_ptr, NULL, NULL); goto done0; } /* read the png info from the file */ png_init_io (png_ptr, fp); png_read_info (png_ptr, info_ptr); /* verify the tEXt attributes defined by the thumbnail spec */ n_text = png_get_text (png_ptr, info_ptr, &text_ptr, &n_text); for (n = 0, n_checked = 0; n_checked < 2 && n < n_text; ++n) { if (strcmp (text_ptr[n].key, "Thumb::MTime") == 0) { /* verify the modification time */ if (G_UNLIKELY (strtol (text_ptr[n].text, NULL, 10) != mtime)) goto done1; ++n_checked; } else if (strcmp (text_ptr[n].key, "Thumb::URI") == 0) { /* check if the URIs are equal */ if (strcmp (text_ptr[n].text, uri) != 0) goto done1; ++n_checked; } } /* check if all required attributes were checked successfully */ is_valid = (n_checked == 2); done1: png_destroy_read_struct (&png_ptr, &info_ptr, NULL); done0: fclose (fp); return is_valid; }