379 lines
13 KiB
C++
379 lines
13 KiB
C++
#include "fileinfo.h"
|
|
#include "fileinfo_p.h"
|
|
#include <gio/gio.h>
|
|
|
|
namespace Fm {
|
|
|
|
const char gfile_info_query_attribs[] = "standard::*,"
|
|
"unix::*,"
|
|
"time::*,"
|
|
"access::*,"
|
|
"id::filesystem,"
|
|
"metadata::emblems";
|
|
|
|
FileInfo::FileInfo() {
|
|
// FIXME: initialize numeric data members
|
|
}
|
|
|
|
FileInfo::FileInfo(const GFileInfoPtr& inf, const FilePath& parentDirPath) {
|
|
setFromGFileInfo(inf, parentDirPath);
|
|
}
|
|
|
|
FileInfo::~FileInfo() {
|
|
}
|
|
|
|
void FileInfo::setFromGFileInfo(const GObjectPtr<GFileInfo>& inf, const FilePath& parentDirPath) {
|
|
dirPath_ = parentDirPath;
|
|
const char* tmp, *uri;
|
|
GIcon* gicon;
|
|
GFileType type;
|
|
|
|
name_ = g_file_info_get_name(inf.get());
|
|
|
|
dispName_ = g_file_info_get_display_name(inf.get());
|
|
|
|
size_ = g_file_info_get_size(inf.get());
|
|
|
|
tmp = g_file_info_get_content_type(inf.get());
|
|
if(!tmp) {
|
|
tmp = "application/octet-stream";
|
|
}
|
|
mimeType_ = MimeType::fromName(tmp);
|
|
|
|
mode_ = g_file_info_get_attribute_uint32(inf.get(), G_FILE_ATTRIBUTE_UNIX_MODE);
|
|
|
|
uid_ = gid_ = -1;
|
|
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_UNIX_UID)) {
|
|
uid_ = g_file_info_get_attribute_uint32(inf.get(), G_FILE_ATTRIBUTE_UNIX_UID);
|
|
}
|
|
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_UNIX_GID)) {
|
|
gid_ = g_file_info_get_attribute_uint32(inf.get(), G_FILE_ATTRIBUTE_UNIX_GID);
|
|
}
|
|
|
|
type = g_file_info_get_file_type(inf.get());
|
|
if(0 == mode_) { /* if UNIX file mode is not available, compose a fake one. */
|
|
switch(type) {
|
|
case G_FILE_TYPE_REGULAR:
|
|
mode_ |= S_IFREG;
|
|
break;
|
|
case G_FILE_TYPE_DIRECTORY:
|
|
mode_ |= S_IFDIR;
|
|
break;
|
|
case G_FILE_TYPE_SYMBOLIC_LINK:
|
|
mode_ |= S_IFLNK;
|
|
break;
|
|
case G_FILE_TYPE_SHORTCUT:
|
|
break;
|
|
case G_FILE_TYPE_MOUNTABLE:
|
|
break;
|
|
case G_FILE_TYPE_SPECIAL:
|
|
if(mode_) {
|
|
break;
|
|
}
|
|
/* if it's a special file but it doesn't have UNIX mode, compose a fake one. */
|
|
if(strcmp(tmp, "inode/chardevice") == 0) {
|
|
mode_ |= S_IFCHR;
|
|
}
|
|
else if(strcmp(tmp, "inode/blockdevice") == 0) {
|
|
mode_ |= S_IFBLK;
|
|
}
|
|
else if(strcmp(tmp, "inode/fifo") == 0) {
|
|
mode_ |= S_IFIFO;
|
|
}
|
|
#ifdef S_IFSOCK
|
|
else if(strcmp(tmp, "inode/socket") == 0) {
|
|
mode_ |= S_IFSOCK;
|
|
}
|
|
#endif
|
|
break;
|
|
case G_FILE_TYPE_UNKNOWN:
|
|
;
|
|
}
|
|
}
|
|
|
|
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_READ)) {
|
|
isAccessible_ = g_file_info_get_attribute_boolean(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_READ);
|
|
}
|
|
else
|
|
/* assume it's accessible */
|
|
{
|
|
isAccessible_ = true;
|
|
}
|
|
|
|
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE)) {
|
|
isWritable_ = g_file_info_get_attribute_boolean(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
|
|
}
|
|
else
|
|
/* assume it's writable */
|
|
{
|
|
isWritable_ = true;
|
|
}
|
|
|
|
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE)) {
|
|
isDeletable_ = g_file_info_get_attribute_boolean(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE);
|
|
}
|
|
else
|
|
/* assume it's deletable */
|
|
{
|
|
isDeletable_ = true;
|
|
}
|
|
|
|
/* special handling for symlinks */
|
|
if(g_file_info_get_is_symlink(inf.get())) {
|
|
mode_ &= ~S_IFMT; /* reset type */
|
|
mode_ |= S_IFLNK; /* set type to symlink */
|
|
goto _file_is_symlink;
|
|
}
|
|
|
|
isShortcut_ = false;
|
|
|
|
switch(type) {
|
|
case G_FILE_TYPE_SHORTCUT:
|
|
isShortcut_ = true;
|
|
case G_FILE_TYPE_MOUNTABLE:
|
|
uri = g_file_info_get_attribute_string(inf.get(), G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
|
|
if(uri) {
|
|
if(g_str_has_prefix(uri, "file:///")) {
|
|
auto filename = CStrPtr{g_filename_from_uri(uri, nullptr, nullptr)};
|
|
target_ = filename.get();
|
|
}
|
|
else {
|
|
target_ = uri;
|
|
}
|
|
if(!mimeType_) {
|
|
mimeType_ = MimeType::guessFromFileName(target_.c_str());
|
|
}
|
|
}
|
|
|
|
/* if the mime-type is not determined or is unknown */
|
|
if(G_UNLIKELY(!mimeType_ || mimeType_->isUnknownType())) {
|
|
/* FIXME: is this appropriate? */
|
|
if(type == G_FILE_TYPE_SHORTCUT) {
|
|
mimeType_ = MimeType::inodeShortcut();
|
|
}
|
|
else {
|
|
mimeType_ = MimeType::inodeMountPoint();
|
|
}
|
|
}
|
|
break;
|
|
case G_FILE_TYPE_DIRECTORY:
|
|
if(!mimeType_) {
|
|
mimeType_ = MimeType::inodeDirectory();
|
|
}
|
|
isReadOnly_ = false; /* default is R/W */
|
|
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_FILESYSTEM_READONLY)) {
|
|
isReadOnly_ = g_file_info_get_attribute_boolean(inf.get(), G_FILE_ATTRIBUTE_FILESYSTEM_READONLY);
|
|
}
|
|
/* directories should be writable to be deleted by user */
|
|
if(isReadOnly_ || !isWritable_) {
|
|
isDeletable_ = false;
|
|
}
|
|
break;
|
|
case G_FILE_TYPE_SYMBOLIC_LINK:
|
|
_file_is_symlink:
|
|
uri = g_file_info_get_symlink_target(inf.get());
|
|
if(uri) {
|
|
if(g_str_has_prefix(uri, "file:///")) {
|
|
auto filename = CStrPtr{g_filename_from_uri(uri, nullptr, nullptr)};
|
|
target_ = filename.get();
|
|
}
|
|
else {
|
|
target_ = uri;
|
|
}
|
|
if(!mimeType_) {
|
|
mimeType_ = MimeType::guessFromFileName(target_.c_str());
|
|
}
|
|
}
|
|
/* continue with absent mime type */
|
|
default: /* G_FILE_TYPE_UNKNOWN G_FILE_TYPE_REGULAR G_FILE_TYPE_SPECIAL */
|
|
if(G_UNLIKELY(!mimeType_)) {
|
|
if(!mimeType_) {
|
|
mimeType_ = MimeType::guessFromFileName(name_.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
/* if there is a custom folder icon, use it */
|
|
if(isNative() && type == G_FILE_TYPE_DIRECTORY) {
|
|
auto local_path = path().localPath();
|
|
auto dot_dir = CStrPtr{g_build_filename(local_path.get(), ".directory", nullptr)};
|
|
if(g_file_test(dot_dir.get(), G_FILE_TEST_IS_REGULAR)) {
|
|
GKeyFile* kf = g_key_file_new();
|
|
if(g_key_file_load_from_file(kf, dot_dir.get(), G_KEY_FILE_NONE, nullptr)) {
|
|
CStrPtr icon_name{g_key_file_get_string(kf, "Desktop Entry", "Icon", nullptr)};
|
|
if(icon_name) {
|
|
auto dot_icon = IconInfo::fromName(icon_name.get());
|
|
if(dot_icon && dot_icon->isValid()) {
|
|
icon_ = dot_icon;
|
|
}
|
|
}
|
|
}
|
|
g_key_file_free(kf);
|
|
}
|
|
}
|
|
|
|
if(!icon_) {
|
|
/* try file-specific icon first */
|
|
gicon = g_file_info_get_icon(inf.get());
|
|
if(gicon) {
|
|
icon_ = IconInfo::fromGIcon(gicon);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
/* set "locked" icon on unaccesible folder */
|
|
else if(!accessible && type == G_FILE_TYPE_DIRECTORY) {
|
|
icon = g_object_ref(icon_locked_folder);
|
|
}
|
|
else {
|
|
icon = g_object_ref(fm_mime_type_get_icon(mime_type));
|
|
}
|
|
#endif
|
|
|
|
/* if the file has emblems, add them to the icon */
|
|
auto emblem_names = g_file_info_get_attribute_stringv(inf.get(), "metadata::emblems");
|
|
if(emblem_names) {
|
|
auto n_emblems = g_strv_length(emblem_names);
|
|
for(int i = n_emblems - 1; i >= 0; --i) {
|
|
emblems_.emplace_front(Fm::IconInfo::fromName(emblem_names[i]));
|
|
}
|
|
}
|
|
|
|
tmp = g_file_info_get_attribute_string(inf.get(), G_FILE_ATTRIBUTE_ID_FILESYSTEM);
|
|
filesystemId_ = g_intern_string(tmp);
|
|
|
|
mtime_ = g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_TIME_MODIFIED);
|
|
atime_ = g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_TIME_ACCESS);
|
|
ctime_ = g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_TIME_CHANGED);
|
|
isHidden_ = g_file_info_get_is_hidden(inf.get());
|
|
isBackup_ = g_file_info_get_is_backup(inf.get());
|
|
isNameChangeable_ = true; /* GVFS tends to ignore this attribute */
|
|
isIconChangeable_ = isHiddenChangeable_ = false;
|
|
if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME)) {
|
|
isNameChangeable_ = g_file_info_get_attribute_boolean(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME);
|
|
}
|
|
|
|
// special handling for desktop entry files (show the name and icon defined in the desktop entry instead)
|
|
if(isNative() && G_UNLIKELY(isDesktopEntry())) {
|
|
auto local_path = path().localPath();
|
|
GKeyFile* kf = g_key_file_new();
|
|
if(g_key_file_load_from_file(kf, local_path.get(), G_KEY_FILE_NONE, nullptr)) {
|
|
/* check if type is correct and supported */
|
|
CStrPtr type{g_key_file_get_string(kf, "Desktop Entry", "Type", nullptr)};
|
|
if(type) {
|
|
// Type == "Link"
|
|
if(strcmp(type.get(), G_KEY_FILE_DESKTOP_TYPE_LINK) == 0) {
|
|
CStrPtr uri{g_key_file_get_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_URL, nullptr)};
|
|
if(uri) {
|
|
isShortcut_ = true;
|
|
target_ = uri.get();
|
|
}
|
|
}
|
|
}
|
|
CStrPtr icon_name{g_key_file_get_string(kf, "Desktop Entry", "Icon", nullptr)};
|
|
if(icon_name) {
|
|
icon_ = IconInfo::fromName(icon_name.get());
|
|
}
|
|
/* Use title of the desktop entry for display */
|
|
CStrPtr displayName{g_key_file_get_locale_string(kf, "Desktop Entry", "Name", nullptr, nullptr)};
|
|
if(displayName) {
|
|
dispName_ = displayName.get();
|
|
}
|
|
/* handle 'Hidden' key to set hidden attribute */
|
|
if(!isHidden_) {
|
|
isHidden_ = g_key_file_get_boolean(kf, "Desktop Entry", "Hidden", nullptr);
|
|
}
|
|
}
|
|
g_key_file_free(kf);
|
|
}
|
|
|
|
if(!icon_ && mimeType_)
|
|
icon_ = mimeType_->icon();
|
|
|
|
#if 0
|
|
GFile* _gf = nullptr;
|
|
GFileAttributeInfoList* list;
|
|
auto list = g_file_query_settable_attributes(gf, nullptr, nullptr);
|
|
if(G_LIKELY(list)) {
|
|
if(g_file_attribute_info_list_lookup(list, G_FILE_ATTRIBUTE_STANDARD_ICON)) {
|
|
icon_is_changeable = true;
|
|
}
|
|
if(g_file_attribute_info_list_lookup(list, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN)) {
|
|
hidden_is_changeable = true;
|
|
}
|
|
g_file_attribute_info_list_unref(list);
|
|
}
|
|
if(G_UNLIKELY(_gf)) {
|
|
g_object_unref(_gf);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FileInfo::bindCutFiles(const std::shared_ptr<const HashSet>& cutFilesHashSet) {
|
|
cutFilesHashSet_ = cutFilesHashSet;
|
|
}
|
|
|
|
bool FileInfo::canThumbnail() const {
|
|
/* We cannot use S_ISREG here as this exclude all symlinks */
|
|
if(size_ == 0 || /* don't generate thumbnails for empty files */
|
|
!(mode_ & S_IFREG) ||
|
|
isDesktopEntry() ||
|
|
isUnknownType()) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* full path of the file is required by this function */
|
|
bool FileInfo::isExecutableType() const {
|
|
if(isText()) { /* g_content_type_can_be_executable reports text files as executables too */
|
|
/* We don't execute remote files nor files in trash */
|
|
if(isNative() && (mode_ & (S_IXOTH | S_IXGRP | S_IXUSR))) {
|
|
/* it has executable bits so lets check shell-bang */
|
|
auto pathStr = path().toString();
|
|
int fd = open(pathStr.get(), O_RDONLY);
|
|
if(fd >= 0) {
|
|
char buf[2];
|
|
ssize_t rdlen = read(fd, &buf, 2);
|
|
close(fd);
|
|
if(rdlen == 2 && buf[0] == '#' && buf[1] == '!') {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
return mimeType_->canBeExecutable();
|
|
}
|
|
|
|
|
|
bool FileInfoList::isSameType() const {
|
|
if(!empty()) {
|
|
auto& item = front();
|
|
for(auto it = cbegin() + 1; it != cend(); ++it) {
|
|
auto& item2 = *it;
|
|
if(item->mimeType() != item2->mimeType()) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FileInfoList::isSameFilesystem() const {
|
|
if(!empty()) {
|
|
auto& item = front();
|
|
for(auto it = cbegin() + 1; it != cend(); ++it) {
|
|
auto& item2 = *it;
|
|
if(item->filesystemId() != item2->filesystemId()) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
} // namespace Fm
|