feat: add torrent-get 'primary-mime-type' to RPC. (#1464)
* feat: add torrent-get 'primary-mime-type' to RPC.
This is a cheap way for RPC clients to know what type of content is in a
torrent. This info can be used to display the torrent, e.g. by using an
icon that corresponds to the mime type.
* use size_t for content byte count
Co-authored-by: Mike Gelfand <mikedld@users.noreply.github.com>
* explicit boolean expressions
Co-authored-by: Mike Gelfand <mikedld@users.noreply.github.com>
* use uint64_t for content byte counts
Co-authored-by: Mike Gelfand <mikedld@users.noreply.github.com>
* avoid unnecessary logic branches
Co-authored-by: Mike Gelfand <mikedld@users.noreply.github.com>
* explicit cast
Co-authored-by: Mike Gelfand <mikedld@users.noreply.github.com>
* refactor: add an autogenerated mime-type.h header
* chore: maybe fix the win32 FTBFS
* chore: add mime-types.[ch] to xcode
* Squashed commit of the following:
commit 4c7153fa48
Author: Mike Gelfand <mikedld@users.noreply.github.com>
Date: Tue Oct 13 03:15:19 2020 +0300
Remove autotools-based build system (#1465)
* Support .git files (e.g. for worktrees, submodules)
* Fix symlinks in source tarball, switch to TXZ, adjust non-release name
* Remove autotools stuff
Co-authored-by: Mike Gelfand <mikedld@users.noreply.github.com>
This commit is contained in:
parent
4c7153fa48
commit
f59118d1fe
|
@ -367,6 +367,8 @@
|
||||||
C1FEE5791C3223CC00D62832 /* watchdir-kqueue.c in Sources */ = {isa = PBXBuildFile; fileRef = C1FEE5741C3223CC00D62832 /* watchdir-kqueue.c */; };
|
C1FEE5791C3223CC00D62832 /* watchdir-kqueue.c in Sources */ = {isa = PBXBuildFile; fileRef = C1FEE5741C3223CC00D62832 /* watchdir-kqueue.c */; };
|
||||||
C1FEE57A1C3223CC00D62832 /* watchdir.c in Sources */ = {isa = PBXBuildFile; fileRef = C1FEE5751C3223CC00D62832 /* watchdir.c */; };
|
C1FEE57A1C3223CC00D62832 /* watchdir.c in Sources */ = {isa = PBXBuildFile; fileRef = C1FEE5751C3223CC00D62832 /* watchdir.c */; };
|
||||||
C1FEE57B1C3223CC00D62832 /* watchdir.h in Headers */ = {isa = PBXBuildFile; fileRef = C1FEE5761C3223CC00D62832 /* watchdir.h */; };
|
C1FEE57B1C3223CC00D62832 /* watchdir.h in Headers */ = {isa = PBXBuildFile; fileRef = C1FEE5761C3223CC00D62832 /* watchdir.h */; };
|
||||||
|
CAB35C64252F6F5E00552A55 /* mime-types.h in Headers */ = {isa = PBXBuildFile; fileRef = CAB35C62252F6F5E00552A55 /* mime-types.h */; };
|
||||||
|
CAB35C65252F6F5E00552A55 /* mime-types.c in Sources */ = {isa = PBXBuildFile; fileRef = CAB35C63252F6F5E00552A55 /* mime-types.c */; };
|
||||||
D4AF3B2F0C41F7A500D46B6B /* list.c in Sources */ = {isa = PBXBuildFile; fileRef = D4AF3B2D0C41F7A500D46B6B /* list.c */; };
|
D4AF3B2F0C41F7A500D46B6B /* list.c in Sources */ = {isa = PBXBuildFile; fileRef = D4AF3B2D0C41F7A500D46B6B /* list.c */; };
|
||||||
D4AF3B300C41F7A600D46B6B /* list.h in Headers */ = {isa = PBXBuildFile; fileRef = D4AF3B2E0C41F7A500D46B6B /* list.h */; };
|
D4AF3B300C41F7A600D46B6B /* list.h in Headers */ = {isa = PBXBuildFile; fileRef = D4AF3B2E0C41F7A500D46B6B /* list.h */; };
|
||||||
E138A9780C04D88F00C5426C /* ProgressGradients.m in Sources */ = {isa = PBXBuildFile; fileRef = E138A9760C04D88F00C5426C /* ProgressGradients.m */; };
|
E138A9780C04D88F00C5426C /* ProgressGradients.m in Sources */ = {isa = PBXBuildFile; fileRef = E138A9760C04D88F00C5426C /* ProgressGradients.m */; };
|
||||||
|
@ -1018,6 +1020,8 @@
|
||||||
C1FEE5741C3223CC00D62832 /* watchdir-kqueue.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "watchdir-kqueue.c"; sourceTree = "<group>"; };
|
C1FEE5741C3223CC00D62832 /* watchdir-kqueue.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "watchdir-kqueue.c"; sourceTree = "<group>"; };
|
||||||
C1FEE5751C3223CC00D62832 /* watchdir.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = watchdir.c; sourceTree = "<group>"; };
|
C1FEE5751C3223CC00D62832 /* watchdir.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = watchdir.c; sourceTree = "<group>"; };
|
||||||
C1FEE5761C3223CC00D62832 /* watchdir.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = watchdir.h; sourceTree = "<group>"; };
|
C1FEE5761C3223CC00D62832 /* watchdir.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = watchdir.h; sourceTree = "<group>"; };
|
||||||
|
CAB35C62252F6F5E00552A55 /* mime-types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mime-types.h"; sourceTree = "<group>"; };
|
||||||
|
CAB35C63252F6F5E00552A55 /* mime-types.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mime-types.c"; sourceTree = "<group>"; };
|
||||||
D4AF3B2D0C41F7A500D46B6B /* list.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = list.c; sourceTree = "<group>"; };
|
D4AF3B2D0C41F7A500D46B6B /* list.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = list.c; sourceTree = "<group>"; };
|
||||||
D4AF3B2E0C41F7A500D46B6B /* list.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = list.h; sourceTree = "<group>"; };
|
D4AF3B2E0C41F7A500D46B6B /* list.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = list.h; sourceTree = "<group>"; };
|
||||||
E138A9750C04D88F00C5426C /* ProgressGradients.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ProgressGradients.h; sourceTree = "<group>"; };
|
E138A9750C04D88F00C5426C /* ProgressGradients.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ProgressGradients.h; sourceTree = "<group>"; };
|
||||||
|
@ -1368,6 +1372,8 @@
|
||||||
4D1838DC09DEC04A0047D688 /* libtransmission */ = {
|
4D1838DC09DEC04A0047D688 /* libtransmission */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
CAB35C63252F6F5E00552A55 /* mime-types.c */,
|
||||||
|
CAB35C62252F6F5E00552A55 /* mime-types.h */,
|
||||||
C1077A4A183EB29600634C22 /* error.c */,
|
C1077A4A183EB29600634C22 /* error.c */,
|
||||||
C1077A4B183EB29600634C22 /* error.h */,
|
C1077A4B183EB29600634C22 /* error.h */,
|
||||||
C1077A4C183EB29600634C22 /* file-posix.c */,
|
C1077A4C183EB29600634C22 /* file-posix.c */,
|
||||||
|
@ -1866,6 +1872,7 @@
|
||||||
A247A443114C701800547DFC /* InfoViewController.h in Headers */,
|
A247A443114C701800547DFC /* InfoViewController.h in Headers */,
|
||||||
A220EC5C118C8A060022B4BE /* tr-lpd.h in Headers */,
|
A220EC5C118C8A060022B4BE /* tr-lpd.h in Headers */,
|
||||||
A23547E311CD0B090046EAE6 /* cache.h in Headers */,
|
A23547E311CD0B090046EAE6 /* cache.h in Headers */,
|
||||||
|
CAB35C64252F6F5E00552A55 /* mime-types.h in Headers */,
|
||||||
A284214512DA663E00FBDDBB /* tr-udp.h in Headers */,
|
A284214512DA663E00FBDDBB /* tr-udp.h in Headers */,
|
||||||
C1077A4F183EB29600634C22 /* error.h in Headers */,
|
C1077A4F183EB29600634C22 /* error.h in Headers */,
|
||||||
A2679295130E00A000CB7464 /* tr-utp.h in Headers */,
|
A2679295130E00A000CB7464 /* tr-utp.h in Headers */,
|
||||||
|
@ -2173,7 +2180,6 @@
|
||||||
29B97313FDCFA39411CA2CEA /* Project object */ = {
|
29B97313FDCFA39411CA2CEA /* Project object */ = {
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = YES;
|
|
||||||
LastUpgradeCheck = 0420;
|
LastUpgradeCheck = 0420;
|
||||||
ORGANIZATIONNAME = "The Transmission Project";
|
ORGANIZATIONNAME = "The Transmission Project";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
|
@ -2204,6 +2210,7 @@
|
||||||
de,
|
de,
|
||||||
da,
|
da,
|
||||||
"pt-PT",
|
"pt-PT",
|
||||||
|
pt_PT,
|
||||||
);
|
);
|
||||||
mainGroup = 29B97314FDCFA39411CA2CEA /* Transmission */;
|
mainGroup = 29B97314FDCFA39411CA2CEA /* Transmission */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
|
@ -2414,6 +2421,7 @@
|
||||||
A201527E0D1C270F0081714F /* torrent-ctor.c in Sources */,
|
A201527E0D1C270F0081714F /* torrent-ctor.c in Sources */,
|
||||||
A2D22A130D65EEE700007D5F /* verify.c in Sources */,
|
A2D22A130D65EEE700007D5F /* verify.c in Sources */,
|
||||||
4D4ADFC70DA1631500A68297 /* blocklist.c in Sources */,
|
4D4ADFC70DA1631500A68297 /* blocklist.c in Sources */,
|
||||||
|
CAB35C65252F6F5E00552A55 /* mime-types.c in Sources */,
|
||||||
A29DF8B90DB2544C00D04E5A /* resume.c in Sources */,
|
A29DF8B90DB2544C00D04E5A /* resume.c in Sources */,
|
||||||
A2A4E9220DE0F7EB000CE197 /* web.c in Sources */,
|
A2A4E9220DE0F7EB000CE197 /* web.c in Sources */,
|
||||||
A2A4EA0E0DE106EB000CE197 /* ConvertUTF.c in Sources */,
|
A2A4EA0E0DE106EB000CE197 /* ConvertUTF.c in Sources */,
|
||||||
|
|
|
@ -195,6 +195,7 @@
|
||||||
errorString | string | tr_stat
|
errorString | string | tr_stat
|
||||||
eta | number | tr_stat
|
eta | number | tr_stat
|
||||||
etaIdle | number | tr_stat
|
etaIdle | number | tr_stat
|
||||||
|
file-count | number | tr_info
|
||||||
files | array (see below) | n/a
|
files | array (see below) | n/a
|
||||||
fileStats | array (see below) | n/a
|
fileStats | array (see below) | n/a
|
||||||
hashString | string | tr_info
|
hashString | string | tr_info
|
||||||
|
@ -223,6 +224,7 @@
|
||||||
pieceCount | number | tr_info
|
pieceCount | number | tr_info
|
||||||
pieceSize | number | tr_info
|
pieceSize | number | tr_info
|
||||||
priorities | array (see below) | n/a
|
priorities | array (see below) | n/a
|
||||||
|
primary-mime-type | string | tr_torrent
|
||||||
queuePosition | number | tr_stat
|
queuePosition | number | tr_stat
|
||||||
rateDownload (B/s) | number | tr_stat
|
rateDownload (B/s) | number | tr_stat
|
||||||
rateUpload (B/s) | number | tr_stat
|
rateUpload (B/s) | number | tr_stat
|
||||||
|
@ -809,6 +811,9 @@
|
||||||
| | yes | torrent-set | new arg "labels"
|
| | yes | torrent-set | new arg "labels"
|
||||||
| | yes | torrent-get | new arg "editDate"
|
| | yes | torrent-get | new arg "editDate"
|
||||||
| | yes | torrent-get | new request arg "format"
|
| | yes | torrent-get | new request arg "format"
|
||||||
|
------+---------+-----------+----------------------+-------------------------------
|
||||||
|
17 | 3.01 | yes | torrent-get | new arg "file-count"
|
||||||
|
| | yes | torrent-get | new arg "primary-mime-type"
|
||||||
|
|
||||||
|
|
||||||
5.1. Upcoming Breakage
|
5.1. Upcoming Breakage
|
||||||
|
|
|
@ -34,6 +34,7 @@ set(PROJECT_FILES
|
||||||
magnet.c
|
magnet.c
|
||||||
makemeta.c
|
makemeta.c
|
||||||
metainfo.c
|
metainfo.c
|
||||||
|
mime-types.c
|
||||||
natpmp.c
|
natpmp.c
|
||||||
net.c
|
net.c
|
||||||
peer-io.c
|
peer-io.c
|
||||||
|
@ -157,6 +158,7 @@ set(${PROJECT_NAME}_PRIVATE_HEADERS
|
||||||
list.h
|
list.h
|
||||||
magnet.h
|
magnet.h
|
||||||
metainfo.h
|
metainfo.h
|
||||||
|
mime-types.h
|
||||||
natpmp_local.h
|
natpmp_local.h
|
||||||
net.h
|
net.h
|
||||||
peer-common.h
|
peer-common.h
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* This file Copyright (C) 2020 Mnemosyne LLC
|
||||||
|
*
|
||||||
|
* It may be used under the GNU GPL versions 2 or 3
|
||||||
|
* or any future license endorsed by Mnemosyne LLC.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define MIME_TYPE_SUFFIX_MAXLEN 24
|
||||||
|
#define MIME_TYPE_SUFFIX_COUNT 1201
|
||||||
|
|
||||||
|
struct mime_type_suffix
|
||||||
|
{
|
||||||
|
char const* suffix;
|
||||||
|
char const* mime_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern struct mime_type_suffix const mime_type_suffixes[MIME_TYPE_SUFFIX_COUNT];
|
|
@ -0,0 +1,72 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const copyright =
|
||||||
|
`/*
|
||||||
|
* This file Copyright (C) ${new Date().getFullYear()} Mnemosyne LLC
|
||||||
|
*
|
||||||
|
* It may be used under the GNU GPL versions 2 or 3
|
||||||
|
* or any future license endorsed by Mnemosyne LLC.
|
||||||
|
*/`;
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const https = require('https');
|
||||||
|
|
||||||
|
// https://github.com/jshttp/mime-db
|
||||||
|
// > If you're crazy enough to use this in the browser, you can just grab
|
||||||
|
// > the JSON file using jsDelivr. It is recommended to replace master with
|
||||||
|
// > a release tag as the JSON format may change in the future.
|
||||||
|
//
|
||||||
|
// This script keeps it at `master` to pick up any fixes that may have landed.
|
||||||
|
// If the format changes, we'll just update this script.
|
||||||
|
const url = 'https://cdn.jsdelivr.net/gh/jshttp/mime-db@master/db.json';
|
||||||
|
|
||||||
|
https.get(url, (res) => {
|
||||||
|
res.setEncoding('utf8');
|
||||||
|
const chunks = [];
|
||||||
|
res.on('data', (chunk) => chunks.push(chunk));
|
||||||
|
res.on('end', () => {
|
||||||
|
try {
|
||||||
|
const suffixes = [];
|
||||||
|
const mime_types = JSON.parse(chunks.join(''));
|
||||||
|
for (const [mime_type, info] of Object.entries(mime_types)) {
|
||||||
|
for (const suffix of info?.extensions || []) {
|
||||||
|
suffixes.push([ suffix, mime_type ]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const max_suffix_len = suffixes
|
||||||
|
.reduce((acc, [suffix]) => Math.max(acc, suffix.length), 0);
|
||||||
|
|
||||||
|
const mime_type_lines = suffixes
|
||||||
|
.map(([suffix, mime_type]) => ` { "${suffix}", "${mime_type}" }`)
|
||||||
|
.sort()
|
||||||
|
.join(',\n');
|
||||||
|
fs.writeFileSync('mime-types.c', `${copyright}
|
||||||
|
|
||||||
|
#include "mime-types.h"
|
||||||
|
|
||||||
|
struct mime_type_suffix const mime_type_suffixes[MIME_TYPE_SUFFIX_COUNT] =
|
||||||
|
{
|
||||||
|
${mime_type_lines}
|
||||||
|
};
|
||||||
|
`);
|
||||||
|
fs.writeFileSync('mime-types.h', `${copyright}
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define MIME_TYPE_SUFFIX_MAXLEN ${max_suffix_len}
|
||||||
|
#define MIME_TYPE_SUFFIX_COUNT ${suffixes.length}
|
||||||
|
|
||||||
|
struct mime_type_suffix
|
||||||
|
{
|
||||||
|
char const* suffix;
|
||||||
|
char const* mime_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern struct mime_type_suffix const mime_type_suffixes[MIME_TYPE_SUFFIX_COUNT];
|
||||||
|
`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -116,6 +116,7 @@ static struct tr_key_struct const my_static[] =
|
||||||
Q("etaIdle"),
|
Q("etaIdle"),
|
||||||
Q("failure reason"),
|
Q("failure reason"),
|
||||||
Q("fields"),
|
Q("fields"),
|
||||||
|
Q("file-count"),
|
||||||
Q("fileStats"),
|
Q("fileStats"),
|
||||||
Q("filename"),
|
Q("filename"),
|
||||||
Q("files"),
|
Q("files"),
|
||||||
|
@ -255,6 +256,7 @@ static struct tr_key_struct const my_static[] =
|
||||||
Q("port-is-open"),
|
Q("port-is-open"),
|
||||||
Q("preallocation"),
|
Q("preallocation"),
|
||||||
Q("prefetch-enabled"),
|
Q("prefetch-enabled"),
|
||||||
|
Q("primary-mime-type"),
|
||||||
Q("priorities"),
|
Q("priorities"),
|
||||||
Q("priority"),
|
Q("priority"),
|
||||||
Q("priority-high"),
|
Q("priority-high"),
|
||||||
|
|
|
@ -115,6 +115,7 @@ enum
|
||||||
TR_KEY_etaIdle,
|
TR_KEY_etaIdle,
|
||||||
TR_KEY_failure_reason,
|
TR_KEY_failure_reason,
|
||||||
TR_KEY_fields,
|
TR_KEY_fields,
|
||||||
|
TR_KEY_file_count,
|
||||||
TR_KEY_fileStats,
|
TR_KEY_fileStats,
|
||||||
TR_KEY_filename,
|
TR_KEY_filename,
|
||||||
TR_KEY_files,
|
TR_KEY_files,
|
||||||
|
@ -254,6 +255,7 @@ enum
|
||||||
TR_KEY_port_is_open,
|
TR_KEY_port_is_open,
|
||||||
TR_KEY_preallocation,
|
TR_KEY_preallocation,
|
||||||
TR_KEY_prefetch_enabled,
|
TR_KEY_prefetch_enabled,
|
||||||
|
TR_KEY_primary_mime_type,
|
||||||
TR_KEY_priorities,
|
TR_KEY_priorities,
|
||||||
TR_KEY_priority,
|
TR_KEY_priority,
|
||||||
TR_KEY_priority_high,
|
TR_KEY_priority_high,
|
||||||
|
|
|
@ -640,6 +640,10 @@ static void initField(tr_torrent* const tor, tr_info const* const inf, tr_stat c
|
||||||
tr_variantInitInt(initme, st->eta);
|
tr_variantInitInt(initme, st->eta);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case TR_KEY_file_count:
|
||||||
|
tr_variantInitInt(initme, inf->fileCount);
|
||||||
|
break;
|
||||||
|
|
||||||
case TR_KEY_files:
|
case TR_KEY_files:
|
||||||
tr_variantInitList(initme, inf->fileCount);
|
tr_variantInitList(initme, inf->fileCount);
|
||||||
addFiles(tor, initme);
|
addFiles(tor, initme);
|
||||||
|
@ -779,6 +783,10 @@ static void initField(tr_torrent* const tor, tr_info const* const inf, tr_stat c
|
||||||
tr_variantInitInt(initme, inf->pieceSize);
|
tr_variantInitInt(initme, inf->pieceSize);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case TR_KEY_primary_mime_type:
|
||||||
|
tr_variantInitStr(initme, tr_torrentPrimaryMimeType(tor), TR_BAD_SIZE);
|
||||||
|
break;
|
||||||
|
|
||||||
case TR_KEY_priorities:
|
case TR_KEY_priorities:
|
||||||
tr_variantInitList(initme, inf->fileCount);
|
tr_variantInitList(initme, inf->fileCount);
|
||||||
for (tr_file_index_t i = 0; i < inf->fileCount; ++i)
|
for (tr_file_index_t i = 0; i < inf->fileCount; ++i)
|
||||||
|
|
|
@ -3378,6 +3378,58 @@ void tr_torrentSetLocation(tr_torrent* tor, char const* location, bool move_from
|
||||||
tr_runInEventThread(tor->session, setLocation, data);
|
tr_runInEventThread(tor->session, setLocation, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char const* tr_torrentPrimaryMimeType(tr_torrent const* tor)
|
||||||
|
{
|
||||||
|
struct count
|
||||||
|
{
|
||||||
|
uint64_t length;
|
||||||
|
char const* mime_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
tr_info const* inf = &tor->info;
|
||||||
|
struct count* counts = tr_new0(struct count, inf->fileCount);
|
||||||
|
size_t num_counts = 0;
|
||||||
|
|
||||||
|
for (tr_file const* it = inf->files, * end = it + inf->fileCount; it != end; ++it)
|
||||||
|
{
|
||||||
|
char const* mime_type = tr_get_mime_type_for_filename(it->name);
|
||||||
|
size_t i;
|
||||||
|
for (i = 0; i < num_counts; ++i)
|
||||||
|
{
|
||||||
|
if (counts[i].mime_type == mime_type)
|
||||||
|
{
|
||||||
|
counts[i].length += it->length;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == num_counts)
|
||||||
|
{
|
||||||
|
counts[i].mime_type = mime_type;
|
||||||
|
counts[i].length = it->length;
|
||||||
|
++num_counts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t max_len = 0;
|
||||||
|
char const* mime_type = NULL;
|
||||||
|
for (struct count const* it = counts, *end = it + num_counts; it != end; ++it)
|
||||||
|
{
|
||||||
|
if ((max_len < it->length) && (it->mime_type != NULL))
|
||||||
|
{
|
||||||
|
max_len = it->length;
|
||||||
|
mime_type = it->mime_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tr_free(counts);
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
|
||||||
|
// application/octet-stream is the default value for all other cases.
|
||||||
|
// An unknown file type should use this type.
|
||||||
|
return mime_type != NULL ? mime_type : "application/octet-stream";
|
||||||
|
}
|
||||||
|
|
||||||
/***
|
/***
|
||||||
****
|
****
|
||||||
***/
|
***/
|
||||||
|
|
|
@ -96,6 +96,10 @@ void tr_torrentSetDateActive(tr_torrent* torrent, time_t activityDate);
|
||||||
|
|
||||||
void tr_torrentSetDateDone(tr_torrent* torrent, time_t doneDate);
|
void tr_torrentSetDateDone(tr_torrent* torrent, time_t doneDate);
|
||||||
|
|
||||||
|
/** Return the mime-type (e.g. "audio/x-flac") that matches more of the
|
||||||
|
torrent's content than any other mime-type. */
|
||||||
|
char const* tr_torrentPrimaryMimeType(tr_torrent const* tor);
|
||||||
|
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
TR_VERIFY_NONE,
|
TR_VERIFY_NONE,
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
#include "ConvertUTF.h"
|
#include "ConvertUTF.h"
|
||||||
#include "list.h"
|
#include "list.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "mime-types.h"
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
#include "platform.h" /* tr_lockLock() */
|
#include "platform.h" /* tr_lockLock() */
|
||||||
#include "platform-quota.h" /* tr_device_info_create(), tr_device_info_get_free_space(), tr_device_info_free() */
|
#include "platform-quota.h" /* tr_device_info_create(), tr_device_info_get_free_space(), tr_device_info_free() */
|
||||||
|
@ -2254,3 +2255,38 @@ void tr_net_init(void)
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// mime-type
|
||||||
|
|
||||||
|
static int compareSuffix(void const* va, void const* vb)
|
||||||
|
{
|
||||||
|
char const* suffix = va;
|
||||||
|
struct mime_type_suffix const* entry = vb;
|
||||||
|
return tr_strcmp0(suffix, entry->suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* tr_get_mime_type_for_filename(char const* filename)
|
||||||
|
{
|
||||||
|
struct mime_type_suffix const* info = NULL;
|
||||||
|
|
||||||
|
char const* in = strrchr(filename, '.');
|
||||||
|
if ((in != NULL) && (strlen(++in) <= MIME_TYPE_SUFFIX_MAXLEN))
|
||||||
|
{
|
||||||
|
char lowercase_suffix[MIME_TYPE_SUFFIX_MAXLEN + 1];
|
||||||
|
char* out = lowercase_suffix;
|
||||||
|
while (*in != '\0')
|
||||||
|
{
|
||||||
|
*out++ = tolower((unsigned char)*in++);
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = '\0';
|
||||||
|
|
||||||
|
info = bsearch(lowercase_suffix,
|
||||||
|
mime_type_suffixes,
|
||||||
|
TR_N_ELEMENTS(mime_type_suffixes),
|
||||||
|
sizeof(*mime_type_suffixes),
|
||||||
|
compareSuffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
return info != NULL ? info->mime_type : NULL;
|
||||||
|
}
|
||||||
|
|
|
@ -60,6 +60,8 @@ char const* tr_strip_positional_args(char const* fmt);
|
||||||
|
|
||||||
#define TR_N_ELEMENTS(ary) (sizeof(ary) / sizeof(*(ary)))
|
#define TR_N_ELEMENTS(ary) (sizeof(ary) / sizeof(*(ary)))
|
||||||
|
|
||||||
|
char const* tr_get_mime_type_for_filename(char const* filename);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Rich Salz's classic implementation of shell-style pattern matching for ?, \, [], and * characters.
|
* @brief Rich Salz's classic implementation of shell-style pattern matching for ?, \, [], and * characters.
|
||||||
* @return 1 if the pattern matches, 0 if it doesn't, or -1 if an error occured
|
* @return 1 if the pattern matches, 0 if it doesn't, or -1 if an error occured
|
||||||
|
|
|
@ -17,14 +17,14 @@
|
||||||
#include <QFileIconProvider>
|
#include <QFileIconProvider>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
|
#include <QMimeDatabase>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QStyle>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <QPixmapCache>
|
#include <QPixmapCache>
|
||||||
#include <QtWin>
|
#include <QtWin>
|
||||||
#else
|
|
||||||
#include <QMimeDatabase>
|
|
||||||
#include <QMimeType>
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <libtransmission/transmission.h>
|
#include <libtransmission/transmission.h>
|
||||||
|
@ -75,6 +75,58 @@ QIcon IconCache::guessMimeIcon(QString const& filename, QIcon fallback) const
|
||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QIcon IconCache::getMimeTypeIcon(QString const& mime_type_name, bool multifile) const
|
||||||
|
{
|
||||||
|
auto& icon = (multifile ? name_to_emblem_icon_ : name_to_icon_)[mime_type_name];
|
||||||
|
|
||||||
|
if (!icon.isNull())
|
||||||
|
{
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!multifile)
|
||||||
|
{
|
||||||
|
QMimeDatabase mime_db;
|
||||||
|
auto const type = mime_db.mimeTypeForName(mime_type_name);
|
||||||
|
icon = QIcon::fromTheme(type.iconName());
|
||||||
|
|
||||||
|
if (icon.isNull())
|
||||||
|
{
|
||||||
|
icon = QIcon::fromTheme(type.genericIconName());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (icon.isNull())
|
||||||
|
{
|
||||||
|
icon = file_icon_;
|
||||||
|
}
|
||||||
|
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const mime_icon = getMimeTypeIcon(mime_type_name, false);
|
||||||
|
for (auto const& size : { QSize(24, 24), QSize(32, 32), QSize(48, 48) })
|
||||||
|
{
|
||||||
|
// upper left corner
|
||||||
|
auto const folder_size = size / 2;
|
||||||
|
auto const folder_rect = QRect(QPoint(), folder_size);
|
||||||
|
|
||||||
|
// fullsize
|
||||||
|
auto const mime_size = size;
|
||||||
|
auto const mime_rect = QRect(QPoint(), mime_size);
|
||||||
|
|
||||||
|
// build the icon
|
||||||
|
auto pixmap = QPixmap(size);
|
||||||
|
pixmap.fill(Qt::transparent);
|
||||||
|
QPainter painter(&pixmap);
|
||||||
|
painter.setRenderHints(QPainter::SmoothPixmapTransform);
|
||||||
|
painter.drawPixmap(folder_rect, folder_icon_.pixmap(folder_size));
|
||||||
|
painter.drawPixmap(mime_rect, mime_icon.pixmap(mime_size));
|
||||||
|
icon.addPixmap(pixmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
/***
|
/***
|
||||||
****
|
****
|
||||||
***/
|
***/
|
||||||
|
@ -132,11 +184,11 @@ QIcon IconCache::getMimeIcon(QString const& filename) const
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
QIcon& icon = icon_cache_[ext];
|
QIcon& icon = ext_to_icon_[ext];
|
||||||
if (icon.isNull()) // cache miss
|
if (icon.isNull()) // cache miss
|
||||||
{
|
{
|
||||||
QMimeDatabase mime_db;
|
QMimeDatabase mime_db;
|
||||||
QMimeType type = mime_db.mimeTypeForFile(filename, QMimeDatabase::MatchExtension);
|
auto const type = mime_db.mimeTypeForFile(filename, QMimeDatabase::MatchExtension);
|
||||||
if (icon.isNull())
|
if (icon.isNull())
|
||||||
{
|
{
|
||||||
icon = QIcon::fromTheme(type.iconName());
|
icon = QIcon::fromTheme(type.iconName());
|
||||||
|
|
|
@ -30,6 +30,7 @@ public:
|
||||||
QIcon folderIcon() const { return folder_icon_; }
|
QIcon folderIcon() const { return folder_icon_; }
|
||||||
QIcon fileIcon() const { return file_icon_; }
|
QIcon fileIcon() const { return file_icon_; }
|
||||||
QIcon guessMimeIcon(QString const& filename, QIcon fallback = {}) const;
|
QIcon guessMimeIcon(QString const& filename, QIcon fallback = {}) const;
|
||||||
|
QIcon getMimeTypeIcon(QString const& mime_type, bool multifile) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
IconCache();
|
IconCache();
|
||||||
|
@ -38,11 +39,14 @@ private:
|
||||||
QIcon const folder_icon_;
|
QIcon const folder_icon_;
|
||||||
QIcon const file_icon_;
|
QIcon const file_icon_;
|
||||||
|
|
||||||
|
mutable std::unordered_map<QString, QIcon> name_to_icon_;
|
||||||
|
mutable std::unordered_map<QString, QIcon> name_to_emblem_icon_;
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
void addAssociatedFileIcon(QFileInfo const& file_info, UINT icon_size, QIcon& icon) const;
|
void addAssociatedFileIcon(QFileInfo const& file_info, UINT icon_size, QIcon& icon) const;
|
||||||
#else
|
#else
|
||||||
mutable std::unordered_map<QString, QIcon> icon_cache_;
|
|
||||||
mutable std::unordered_set<QString> suffixes_;
|
mutable std::unordered_set<QString> suffixes_;
|
||||||
|
mutable std::unordered_map<QString, QIcon> ext_to_icon_;
|
||||||
QIcon getMimeIcon(QString const& filename) const;
|
QIcon getMimeIcon(QString const& filename) const;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
|
@ -539,11 +539,13 @@ std::vector<std::string_view> const& Session::getKeyNames(TorrentProperties prop
|
||||||
if (names.empty())
|
if (names.empty())
|
||||||
{
|
{
|
||||||
// unchanging fields needed by the main window
|
// unchanging fields needed by the main window
|
||||||
static auto constexpr MainInfoKeys = std::array<tr_quark, 6>{
|
static auto constexpr MainInfoKeys = std::array<tr_quark, 8>{
|
||||||
TR_KEY_addedDate,
|
TR_KEY_addedDate,
|
||||||
TR_KEY_downloadDir,
|
TR_KEY_downloadDir,
|
||||||
|
TR_KEY_file_count,
|
||||||
TR_KEY_hashString,
|
TR_KEY_hashString,
|
||||||
TR_KEY_name,
|
TR_KEY_name,
|
||||||
|
TR_KEY_primary_mime_type,
|
||||||
TR_KEY_totalSize,
|
TR_KEY_totalSize,
|
||||||
TR_KEY_trackers,
|
TR_KEY_trackers,
|
||||||
};
|
};
|
||||||
|
|
|
@ -165,21 +165,7 @@ QIcon Torrent::getMimeTypeIcon() const
|
||||||
{
|
{
|
||||||
if (icon_.isNull())
|
if (icon_.isNull())
|
||||||
{
|
{
|
||||||
auto const& files = files_;
|
icon_ = IconCache::get().getMimeTypeIcon(primary_mime_type_, file_count_ > 1);
|
||||||
auto const& icon_cache = IconCache::get();
|
|
||||||
|
|
||||||
if (files.size() > 1)
|
|
||||||
{
|
|
||||||
icon_ = icon_cache.folderIcon();
|
|
||||||
}
|
|
||||||
else if (files.size() == 1)
|
|
||||||
{
|
|
||||||
icon_ = icon_cache.guessMimeIcon(files.at(0).filename, icon_cache.fileIcon());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
icon_ = icon_cache.guessMimeIcon(name(), icon_cache.folderIcon());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return icon_;
|
return icon_;
|
||||||
|
@ -220,6 +206,7 @@ Torrent::fields_t Torrent::update(tr_quark const* keys, tr_variant const* const*
|
||||||
HANDLE_KEY(eta, eta, ETA)
|
HANDLE_KEY(eta, eta, ETA)
|
||||||
HANDLE_KEY(fileStats, files, FILES)
|
HANDLE_KEY(fileStats, files, FILES)
|
||||||
HANDLE_KEY(files, files, FILES)
|
HANDLE_KEY(files, files, FILES)
|
||||||
|
HANDLE_KEY(file_count, file_count, FILE_COUNT)
|
||||||
HANDLE_KEY(hashString, hash, HASH)
|
HANDLE_KEY(hashString, hash, HASH)
|
||||||
HANDLE_KEY(haveUnchecked, have_unchecked, HAVE_UNCHECKED)
|
HANDLE_KEY(haveUnchecked, have_unchecked, HAVE_UNCHECKED)
|
||||||
HANDLE_KEY(haveValid, have_verified, HAVE_VERIFIED)
|
HANDLE_KEY(haveValid, have_verified, HAVE_VERIFIED)
|
||||||
|
@ -239,6 +226,7 @@ Torrent::fields_t Torrent::update(tr_quark const* keys, tr_variant const* const*
|
||||||
HANDLE_KEY(percentDone, percent_done, PERCENT_DONE)
|
HANDLE_KEY(percentDone, percent_done, PERCENT_DONE)
|
||||||
HANDLE_KEY(pieceCount, piece_count, PIECE_COUNT)
|
HANDLE_KEY(pieceCount, piece_count, PIECE_COUNT)
|
||||||
HANDLE_KEY(pieceSize, piece_size, PIECE_SIZE)
|
HANDLE_KEY(pieceSize, piece_size, PIECE_SIZE)
|
||||||
|
HANDLE_KEY(primary_mime_type, primary_mime_type, PRIMARY_MIME_TYPE)
|
||||||
HANDLE_KEY(queuePosition, queue_position, QUEUE_POSITION)
|
HANDLE_KEY(queuePosition, queue_position, QUEUE_POSITION)
|
||||||
HANDLE_KEY(rateDownload, download_speed, DOWNLOAD_SPEED)
|
HANDLE_KEY(rateDownload, download_speed, DOWNLOAD_SPEED)
|
||||||
HANDLE_KEY(rateUpload, upload_speed, UPLOAD_SPEED)
|
HANDLE_KEY(rateUpload, upload_speed, UPLOAD_SPEED)
|
||||||
|
@ -282,7 +270,8 @@ Torrent::fields_t Torrent::update(tr_quark const* keys, tr_variant const* const*
|
||||||
{
|
{
|
||||||
switch (key)
|
switch (key)
|
||||||
{
|
{
|
||||||
case TR_KEY_name:
|
case TR_KEY_file_count:
|
||||||
|
case TR_KEY_primary_mime_type:
|
||||||
{
|
{
|
||||||
icon_ = {};
|
icon_ = {};
|
||||||
break;
|
break;
|
||||||
|
@ -290,7 +279,6 @@ Torrent::fields_t Torrent::update(tr_quark const* keys, tr_variant const* const*
|
||||||
|
|
||||||
case TR_KEY_files:
|
case TR_KEY_files:
|
||||||
{
|
{
|
||||||
icon_ = {};
|
|
||||||
for (int i = 0; i < files_.size(); ++i)
|
for (int i = 0; i < files_.size(); ++i)
|
||||||
{
|
{
|
||||||
files_[i].index = i;
|
files_[i].index = i;
|
||||||
|
|
|
@ -561,6 +561,7 @@ public:
|
||||||
ERROR_STRING,
|
ERROR_STRING,
|
||||||
ETA,
|
ETA,
|
||||||
FAILED_EVER,
|
FAILED_EVER,
|
||||||
|
FILE_COUNT,
|
||||||
FILES,
|
FILES,
|
||||||
HASH,
|
HASH,
|
||||||
HAVE_UNCHECKED,
|
HAVE_UNCHECKED,
|
||||||
|
@ -582,6 +583,7 @@ public:
|
||||||
PERCENT_DONE,
|
PERCENT_DONE,
|
||||||
PIECE_COUNT,
|
PIECE_COUNT,
|
||||||
PIECE_SIZE,
|
PIECE_SIZE,
|
||||||
|
PRIMARY_MIME_TYPE,
|
||||||
QUEUE_POSITION,
|
QUEUE_POSITION,
|
||||||
RECHECK_PROGRESS,
|
RECHECK_PROGRESS,
|
||||||
SEED_IDLE_LIMIT,
|
SEED_IDLE_LIMIT,
|
||||||
|
@ -642,6 +644,7 @@ private:
|
||||||
uint64_t desired_available_ = {};
|
uint64_t desired_available_ = {};
|
||||||
uint64_t downloaded_ever_ = {};
|
uint64_t downloaded_ever_ = {};
|
||||||
uint64_t failed_ever_ = {};
|
uint64_t failed_ever_ = {};
|
||||||
|
uint64_t file_count_ = {};
|
||||||
uint64_t have_unchecked_ = {};
|
uint64_t have_unchecked_ = {};
|
||||||
uint64_t have_verified_ = {};
|
uint64_t have_verified_ = {};
|
||||||
uint64_t left_until_done_ = {};
|
uint64_t left_until_done_ = {};
|
||||||
|
@ -655,6 +658,7 @@ private:
|
||||||
double recheck_progress_ = {};
|
double recheck_progress_ = {};
|
||||||
double seed_ratio_limit_ = {};
|
double seed_ratio_limit_ = {};
|
||||||
|
|
||||||
|
QString primary_mime_type_;
|
||||||
QString comment_;
|
QString comment_;
|
||||||
QString creator_;
|
QString creator_;
|
||||||
QString download_dir_;
|
QString download_dir_;
|
||||||
|
|
|
@ -442,3 +442,12 @@ TEST_F(UtilsTest, env)
|
||||||
s = makeString(tr_env_get_string(test_key, "c"));
|
s = makeString(tr_env_get_string(test_key, "c"));
|
||||||
EXPECT_EQ("135", s);
|
EXPECT_EQ("135", s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(UtilsTest, mimeTypes)
|
||||||
|
{
|
||||||
|
EXPECT_STREQ("audio/x-flac", tr_get_mime_type_for_filename("music.flac"));
|
||||||
|
EXPECT_STREQ("audio/x-flac", tr_get_mime_type_for_filename("music.FLAC"));
|
||||||
|
EXPECT_STREQ("video/x-msvideo", tr_get_mime_type_for_filename(".avi"));
|
||||||
|
EXPECT_STREQ("video/x-msvideo", tr_get_mime_type_for_filename("/path/to/FILENAME.AVI"));
|
||||||
|
EXPECT_EQ(nullptr, tr_get_mime_type_for_filename("music.ajoijfeisfe"));
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue