make file attributes (in file_storage) type safe
This commit is contained in:
parent
35232a5a5f
commit
a8a5986046
|
@ -16,6 +16,7 @@
|
|||
#include "libtorrent/peer_info.hpp"
|
||||
#include "libtorrent/alert_types.hpp" // for picker_flags_t
|
||||
#include "libtorrent/session_types.hpp" // for save_state_flags_t
|
||||
#include "libtorrent/file_storage.hpp" // for file_flags_t
|
||||
#include "libtorrent/alert.hpp"
|
||||
#include <vector>
|
||||
|
||||
|
@ -312,6 +313,7 @@ void bind_converters()
|
|||
to_python_converter<lt::save_state_flags_t, from_bitfield_flag<lt::save_state_flags_t>>();
|
||||
to_python_converter<lt::session_flags_t, from_bitfield_flag<lt::session_flags_t>>();
|
||||
to_python_converter<lt::remove_flags_t, from_bitfield_flag<lt::remove_flags_t>>();
|
||||
to_python_converter<lt::file_flags_t, from_bitfield_flag<lt::file_flags_t>>();
|
||||
|
||||
// work-around types
|
||||
to_python_converter<lt::aux::noexcept_movable<lt::address>, address_to_tuple<
|
||||
|
@ -369,4 +371,5 @@ void bind_converters()
|
|||
to_bitfield_flag<lt::save_state_flags_t>();
|
||||
to_bitfield_flag<lt::session_flags_t>();
|
||||
to_bitfield_flag<lt::remove_flags_t>();
|
||||
to_bitfield_flag<lt::file_flags_t>();
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@ namespace
|
|||
{ return FileIter(self, self.end_file()); }
|
||||
|
||||
void add_file_wstring(file_storage& fs, std::wstring const& file, std::int64_t size
|
||||
, int flags, std::time_t md, std::string link)
|
||||
, file_flags_t const flags, std::time_t md, std::string link)
|
||||
{
|
||||
fs.add_file(file, size, flags, md, link);
|
||||
}
|
||||
|
@ -115,7 +115,7 @@ namespace
|
|||
}
|
||||
|
||||
void add_file(file_storage& fs, std::string const& file, std::int64_t size
|
||||
, int flags, std::time_t md, std::string link)
|
||||
, file_flags_t const flags, std::time_t md, std::string link)
|
||||
{
|
||||
fs.add_file(file, size, flags, md, link);
|
||||
}
|
||||
|
@ -124,6 +124,8 @@ namespace
|
|||
{
|
||||
ct.add_tracker(url, tier);
|
||||
}
|
||||
|
||||
struct dummy13 {};
|
||||
}
|
||||
|
||||
void bind_create_torrent()
|
||||
|
@ -145,14 +147,15 @@ void bind_create_torrent()
|
|||
std::string (file_storage::*file_storage_file_path)(file_index_t, std::string const&) const = &file_storage::file_path;
|
||||
std::int64_t (file_storage::*file_storage_file_size)(file_index_t) const = &file_storage::file_size;
|
||||
std::int64_t (file_storage::*file_storage_file_offset)(file_index_t) const = &file_storage::file_offset;
|
||||
std::uint32_t (file_storage::*file_storage_file_flags)(file_index_t) const = &file_storage::file_flags;
|
||||
file_flags_t (file_storage::*file_storage_file_flags)(file_index_t) const = &file_storage::file_flags;
|
||||
|
||||
#ifndef TORRENT_NO_DEPRECATE
|
||||
file_entry (file_storage::*at)(int) const = &file_storage::at;
|
||||
#endif
|
||||
|
||||
// TODO: 3 move this to its own file
|
||||
class_<file_storage>("file_storage")
|
||||
{
|
||||
scope s = class_<file_storage>("file_storage")
|
||||
.def("is_valid", &file_storage::is_valid)
|
||||
.def("add_file", add_file, (arg("path"), arg("size"), arg("flags") = 0, arg("mtime") = 0, arg("linkpath") = ""))
|
||||
.def("num_files", &file_storage::num_files)
|
||||
|
@ -185,12 +188,19 @@ void bind_create_torrent()
|
|||
.def("name", &file_storage::name, return_value_policy<copy_const_reference>())
|
||||
;
|
||||
|
||||
enum_<file_storage::file_flags_t>("file_flags_t")
|
||||
.value("flag_pad_file", file_storage::flag_pad_file)
|
||||
.value("flag_hidden", file_storage::flag_hidden)
|
||||
.value("flag_executable", file_storage::flag_executable)
|
||||
.value("flag_symlink", file_storage::flag_symlink)
|
||||
;
|
||||
s.attr("flag_pad_file") = file_storage::flag_pad_file;
|
||||
s.attr("flag_hidden") = file_storage::flag_hidden;
|
||||
s.attr("flag_executable") = file_storage::flag_executable;
|
||||
s.attr("flag_symlink") = file_storage::flag_symlink;
|
||||
}
|
||||
|
||||
{
|
||||
scope s = class_<dummy13>("file_flags_t");
|
||||
s.attr("flag_pad_file") = file_storage::flag_pad_file;
|
||||
s.attr("flag_hidden") = file_storage::flag_hidden;
|
||||
s.attr("flag_executable") = file_storage::flag_executable;
|
||||
s.attr("flag_symlink") = file_storage::flag_symlink;
|
||||
}
|
||||
|
||||
class_<create_torrent>("create_torrent", no_init)
|
||||
.def(init<file_storage&>())
|
||||
|
|
|
@ -190,7 +190,7 @@ int main(int argc, char* argv[])
|
|||
{
|
||||
piece_index_t const first = st.map_file(i, 0, 0).piece;
|
||||
piece_index_t const last = st.map_file(i, (std::max)(std::int64_t(st.file_size(i))-1, std::int64_t(0)), 0).piece;
|
||||
int const flags = st.file_flags(i);
|
||||
auto const flags = st.file_flags(i);
|
||||
std::stringstream file_hash;
|
||||
if (!st.hash(i).is_all_zeros())
|
||||
file_hash << st.hash(i);
|
||||
|
|
|
@ -46,6 +46,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
|||
#include "libtorrent/string_view.hpp"
|
||||
#include "libtorrent/aux_/vector.hpp"
|
||||
#include "libtorrent/aux_/noexcept_movable.hpp"
|
||||
#include "libtorrent/flags.hpp"
|
||||
|
||||
namespace libtorrent {
|
||||
|
||||
|
@ -192,6 +193,10 @@ namespace libtorrent {
|
|||
std::int64_t size;
|
||||
};
|
||||
|
||||
// hidden
|
||||
struct file_flags_tag;
|
||||
using file_flags_t = flags::bitfield_flag<std::uint8_t, file_flags_tag>;
|
||||
|
||||
// The ``file_storage`` class represents a file list and the piece
|
||||
// size. Everything necessary to interpret a regular bittorrent storage
|
||||
// file structure.
|
||||
|
@ -214,25 +219,12 @@ namespace libtorrent {
|
|||
// not.
|
||||
bool is_valid() const { return m_piece_length > 0; }
|
||||
|
||||
// file attribute flags
|
||||
enum flags_t
|
||||
{
|
||||
// the file is a pad file. It's required to contain zeros
|
||||
// at it will not be saved to disk. Its purpose is to make
|
||||
// the following file start on a piece boundary.
|
||||
pad_file = 1,
|
||||
|
||||
// this file has the hidden attribute set. This is primarily
|
||||
// a windows attribute
|
||||
attribute_hidden = 2,
|
||||
|
||||
// this file has the executable attribute set.
|
||||
attribute_executable = 4,
|
||||
|
||||
// this file is a symbolic link. It should have a link
|
||||
// target string associated with it.
|
||||
attribute_symlink = 8
|
||||
};
|
||||
#ifndef TORRENT_NO_DEPRECATE
|
||||
static constexpr file_flags_t TORRENT_DEPRECATED_MEMBER pad_file = 0_bit;
|
||||
static constexpr file_flags_t TORRENT_DEPRECATED_MEMBER attribute_hidden = 1_bit;
|
||||
static constexpr file_flags_t TORRENT_DEPRECATED_MEMBER attribute_executable = 2_bit;
|
||||
static constexpr file_flags_t TORRENT_DEPRECATED_MEMBER attribute_symlink = 3_bit;
|
||||
#endif
|
||||
|
||||
// allocates space for ``num_files`` in the internal file list. This can
|
||||
// be used to avoid reallocating the internal file list when the number
|
||||
|
@ -278,9 +270,10 @@ namespace libtorrent {
|
|||
// can be changed by calling ``set_name``.
|
||||
void add_file_borrow(char const* filename, int filename_len
|
||||
, std::string const& path, std::int64_t file_size
|
||||
, std::uint32_t file_flags = 0, char const* filehash = 0
|
||||
, file_flags_t file_flags = {}, char const* filehash = 0
|
||||
, std::int64_t mtime = 0, string_view symlink_path = string_view());
|
||||
void add_file(std::string const& path, std::int64_t file_size, std::uint32_t file_flags = 0
|
||||
void add_file(std::string const& path, std::int64_t file_size
|
||||
, file_flags_t file_flags = {}
|
||||
, std::time_t mtime = 0, string_view symlink_path = string_view());
|
||||
|
||||
// renames the file at ``index`` to ``new_filename``. Keep in mind
|
||||
|
@ -295,7 +288,8 @@ namespace libtorrent {
|
|||
// instead, use the wchar -> utf8 conversion functions
|
||||
// and pass in utf8 strings
|
||||
TORRENT_DEPRECATED
|
||||
void add_file(std::wstring const& p, std::int64_t size, std::uint32_t flags = 0
|
||||
void add_file(std::wstring const& p, std::int64_t size
|
||||
, file_flags_t flags = {}
|
||||
, std::time_t mtime = 0, string_view s_p = "");
|
||||
TORRENT_DEPRECATED
|
||||
void rename_file(file_index_t index, std::wstring const& new_filename);
|
||||
|
@ -477,35 +471,27 @@ namespace libtorrent {
|
|||
// the set.
|
||||
void all_path_hashes(std::unordered_set<std::uint32_t>& table) const;
|
||||
|
||||
// flags indicating various attributes for files in
|
||||
// a file_storage.
|
||||
enum file_flags_t : std::uint32_t
|
||||
{
|
||||
// this file is a pad file. The creator of the
|
||||
// torrent promises the file is entirely filled with
|
||||
// zeros and does not need to be downloaded. The
|
||||
// purpose is just to align the next file to either
|
||||
// a block or piece boundary.
|
||||
flag_pad_file = 1,
|
||||
// the file is a pad file. It's required to contain zeros
|
||||
// at it will not be saved to disk. Its purpose is to make
|
||||
// the following file start on a piece boundary.
|
||||
static constexpr file_flags_t flag_pad_file = 0_bit;
|
||||
|
||||
// this file is hidden (sets the hidden attribute
|
||||
// on windows)
|
||||
flag_hidden = 2,
|
||||
// this file has the hidden attribute set. This is primarily
|
||||
// a windows attribute
|
||||
static constexpr file_flags_t flag_hidden = 1_bit;
|
||||
|
||||
// this file is executable (sets the executable bit
|
||||
// on posix like systems)
|
||||
flag_executable = 4,
|
||||
// this file has the executable attribute set.
|
||||
static constexpr file_flags_t flag_executable = 2_bit;
|
||||
|
||||
// this file is a symlink. The symlink target is
|
||||
// specified in a separate field
|
||||
flag_symlink = 8
|
||||
};
|
||||
// this file is a symbolic link. It should have a link
|
||||
// target string associated with it.
|
||||
static constexpr file_flags_t flag_symlink = 3_bit;
|
||||
|
||||
std::vector<std::string> const& paths() const { return m_paths; }
|
||||
|
||||
// returns a bitmask of flags from file_flags_t that apply
|
||||
// to file at ``index``.
|
||||
std::uint32_t file_flags(file_index_t index) const;
|
||||
file_flags_t file_flags(file_index_t index) const;
|
||||
|
||||
// returns true if the file at the specified index has been renamed to
|
||||
// have an absolute path, i.e. is not anchored in the save path of the
|
||||
|
|
|
@ -50,31 +50,30 @@ POSSIBILITY OF SUCH DAMAGE.
|
|||
using namespace std::placeholders;
|
||||
|
||||
namespace libtorrent {
|
||||
namespace {
|
||||
|
||||
namespace {
|
||||
bool default_pred(std::string const&) { return true; }
|
||||
|
||||
inline bool default_pred(std::string const&) { return true; }
|
||||
|
||||
inline bool ignore_subdir(std::string const& leaf)
|
||||
bool ignore_subdir(std::string const& leaf)
|
||||
{ return leaf == ".." || leaf == "."; }
|
||||
|
||||
std::uint32_t get_file_attributes(std::string const& p)
|
||||
file_flags_t get_file_attributes(std::string const& p)
|
||||
{
|
||||
#ifdef TORRENT_WINDOWS
|
||||
WIN32_FILE_ATTRIBUTE_DATA attr;
|
||||
std::wstring path = convert_to_wstring(p);
|
||||
GetFileAttributesExW(path.c_str(), GetFileExInfoStandard, &attr);
|
||||
if (attr.dwFileAttributes == INVALID_FILE_ATTRIBUTES) return 0;
|
||||
if (attr.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) return file_storage::attribute_hidden;
|
||||
return 0;
|
||||
if (attr.dwFileAttributes == INVALID_FILE_ATTRIBUTES) return {};
|
||||
if (attr.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) return file_storage::flag_hidden;
|
||||
return {};
|
||||
#else
|
||||
struct ::stat s;
|
||||
if (::lstat(convert_to_native(p).c_str(), &s) < 0) return 0;
|
||||
std::uint32_t file_attr = 0;
|
||||
if (::lstat(convert_to_native(p).c_str(), &s) < 0) return {};
|
||||
file_flags_t file_attr = {};
|
||||
if (s.st_mode & S_IXUSR)
|
||||
file_attr += file_storage::attribute_executable;
|
||||
file_attr |= file_storage::flag_executable;
|
||||
if (S_ISLNK(s.st_mode))
|
||||
file_attr += file_storage::attribute_symlink;
|
||||
file_attr |= file_storage::flag_symlink;
|
||||
return file_attr;
|
||||
#endif
|
||||
}
|
||||
|
@ -137,10 +136,10 @@ namespace libtorrent {
|
|||
else
|
||||
{
|
||||
// #error use the fields from s
|
||||
std::uint32_t file_flags = get_file_attributes(f);
|
||||
file_flags_t const file_flags = get_file_attributes(f);
|
||||
|
||||
// mask all bits to check if the file is a symlink
|
||||
if ((file_flags & file_storage::attribute_symlink)
|
||||
if ((file_flags & file_storage::flag_symlink)
|
||||
&& (flags & create_torrent::symlinks))
|
||||
{
|
||||
std::string sym_path = get_symlink_path(f);
|
||||
|
@ -191,7 +190,7 @@ namespace libtorrent {
|
|||
st->iothread.submit_jobs();
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
} // anonymous namespace
|
||||
|
||||
#ifndef TORRENT_NO_DEPRECATE
|
||||
|
||||
|
@ -526,7 +525,7 @@ namespace libtorrent {
|
|||
file_index_t const first(0);
|
||||
if (m_include_mtime) info["mtime"] = m_files.mtime(first);
|
||||
info["length"] = m_files.file_size(first);
|
||||
std::uint32_t const flags = m_files.file_flags(first);
|
||||
file_flags_t const flags = m_files.file_flags(first);
|
||||
if (flags & (file_storage::flag_pad_file
|
||||
| file_storage::flag_hidden
|
||||
| file_storage::flag_executable
|
||||
|
@ -577,8 +576,8 @@ namespace libtorrent {
|
|||
path_e.list().push_back(entry(e));
|
||||
}
|
||||
|
||||
std::uint32_t const flags = m_files.file_flags(i);
|
||||
if (flags != 0)
|
||||
file_flags_t const flags = m_files.file_flags(i);
|
||||
if (flags)
|
||||
{
|
||||
std::string& attr = file_e["attr"].string();
|
||||
if (flags & file_storage::flag_pad_file) attr += 'p';
|
||||
|
|
|
@ -56,6 +56,18 @@ using namespace std::placeholders;
|
|||
|
||||
namespace libtorrent {
|
||||
|
||||
constexpr file_flags_t file_storage::flag_pad_file;
|
||||
constexpr file_flags_t file_storage::flag_hidden;
|
||||
constexpr file_flags_t file_storage::flag_executable;
|
||||
constexpr file_flags_t file_storage::flag_symlink;
|
||||
|
||||
#ifndef TORRENT_NO_DEPRECATE
|
||||
constexpr file_flags_t file_storage::pad_file;
|
||||
constexpr file_flags_t file_storage::attribute_hidden;
|
||||
constexpr file_flags_t file_storage::attribute_executable;
|
||||
constexpr file_flags_t file_storage::attribute_symlink;
|
||||
#endif
|
||||
|
||||
file_storage::file_storage()
|
||||
: m_piece_length(0)
|
||||
, m_num_pieces(0)
|
||||
|
@ -337,7 +349,7 @@ namespace {
|
|||
|
||||
void file_storage::add_file(file_entry const& fe, char const* filehash)
|
||||
{
|
||||
std::uint32_t flags = 0;
|
||||
file_flags_t flags = {};
|
||||
if (fe.pad_file) flags |= file_storage::flag_pad_file;
|
||||
if (fe.hidden_attribute) flags |= file_storage::flag_hidden;
|
||||
if (fe.executable_attribute) flags |= file_storage::flag_executable;
|
||||
|
@ -359,7 +371,7 @@ namespace {
|
|||
}
|
||||
|
||||
void file_storage::add_file(std::wstring const& file, std::int64_t file_size
|
||||
, std::uint32_t file_flags, std::time_t mtime, string_view symlink_path)
|
||||
, file_flags_t const file_flags, std::time_t mtime, string_view symlink_path)
|
||||
{
|
||||
add_file(wchar_utf8(file), file_size, file_flags, mtime, symlink_path);
|
||||
}
|
||||
|
@ -532,7 +544,7 @@ namespace {
|
|||
}
|
||||
|
||||
void file_storage::add_file(std::string const& path, std::int64_t file_size
|
||||
, std::uint32_t file_flags, std::time_t mtime, string_view symlink_path)
|
||||
, file_flags_t const file_flags, std::time_t mtime, string_view symlink_path)
|
||||
{
|
||||
add_file_borrow(nullptr, 0, path, file_size, file_flags, nullptr, mtime
|
||||
, symlink_path);
|
||||
|
@ -540,7 +552,7 @@ namespace {
|
|||
|
||||
void file_storage::add_file_borrow(char const* filename, int const filename_len
|
||||
, std::string const& path, std::int64_t const file_size
|
||||
, std::uint32_t const file_flags, char const* filehash
|
||||
, file_flags_t const file_flags, char const* filehash
|
||||
, std::int64_t const mtime, string_view symlink_path)
|
||||
{
|
||||
TORRENT_ASSERT_PRECOND(file_size >= 0);
|
||||
|
@ -576,10 +588,10 @@ namespace {
|
|||
|
||||
e.size = aux::numeric_cast<std::uint64_t>(file_size);
|
||||
e.offset = aux::numeric_cast<std::uint64_t>(m_total_size);
|
||||
e.pad_file = (file_flags & file_storage::flag_pad_file) != 0;
|
||||
e.hidden_attribute = (file_flags & file_storage::flag_hidden) != 0;
|
||||
e.executable_attribute = (file_flags & file_storage::flag_executable) != 0;
|
||||
e.symlink_attribute = (file_flags & file_storage::flag_symlink) != 0;
|
||||
e.pad_file = bool(file_flags & file_storage::flag_pad_file);
|
||||
e.hidden_attribute = bool(file_flags & file_storage::flag_hidden);
|
||||
e.executable_attribute = bool(file_flags & file_storage::flag_executable);
|
||||
e.symlink_attribute = bool(file_flags & file_storage::flag_symlink);
|
||||
|
||||
if (filehash)
|
||||
{
|
||||
|
@ -805,14 +817,14 @@ namespace {
|
|||
return m_files[index].offset;
|
||||
}
|
||||
|
||||
std::uint32_t file_storage::file_flags(file_index_t const index) const
|
||||
file_flags_t file_storage::file_flags(file_index_t const index) const
|
||||
{
|
||||
TORRENT_ASSERT_PRECOND(index >= file_index_t(0) && index < end_file());
|
||||
internal_file_entry const& fe = m_files[index];
|
||||
return (fe.pad_file ? flag_pad_file : 0u)
|
||||
| (fe.hidden_attribute ? flag_hidden : 0u)
|
||||
| (fe.executable_attribute ? flag_executable : 0u)
|
||||
| (fe.symlink_attribute ? flag_symlink : 0u);
|
||||
return (fe.pad_file ? file_storage::flag_pad_file : file_flags_t{})
|
||||
| (fe.hidden_attribute ? file_storage::flag_hidden : file_flags_t{})
|
||||
| (fe.executable_attribute ? file_storage::flag_executable : file_flags_t{})
|
||||
| (fe.symlink_attribute ? file_storage::flag_symlink : file_flags_t{});
|
||||
}
|
||||
|
||||
bool file_storage::file_absolute_path(file_index_t const index) const
|
||||
|
|
|
@ -317,11 +317,11 @@ namespace libtorrent {
|
|||
if (path.empty()) path = "_";
|
||||
}
|
||||
|
||||
namespace {
|
||||
namespace {
|
||||
|
||||
std::uint32_t get_file_attributes(bdecode_node const& dict)
|
||||
file_flags_t get_file_attributes(bdecode_node const& dict)
|
||||
{
|
||||
std::uint32_t file_flags = 0;
|
||||
file_flags_t file_flags = {};
|
||||
bdecode_node const attr = dict.dict_find_string("attr");
|
||||
if (attr)
|
||||
{
|
||||
|
@ -370,7 +370,7 @@ namespace libtorrent {
|
|||
{
|
||||
if (dict.type() != bdecode_node::dict_t) return false;
|
||||
|
||||
std::uint32_t file_flags = get_file_attributes(dict);
|
||||
file_flags_t file_flags = get_file_attributes(dict);
|
||||
|
||||
// symlinks have an implied "size" of zero. i.e. they use up 0 bytes of
|
||||
// the torrent payload space
|
||||
|
@ -445,7 +445,7 @@ namespace libtorrent {
|
|||
|
||||
// bitcomet pad file
|
||||
if (path.find("_____padding_file_") != std::string::npos)
|
||||
file_flags = file_storage::flag_pad_file;
|
||||
file_flags |= file_storage::flag_pad_file;
|
||||
|
||||
bdecode_node const fh = dict.dict_find_string("sha1");
|
||||
char const* filehash = nullptr;
|
||||
|
@ -566,7 +566,7 @@ namespace libtorrent {
|
|||
return 0;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
} // anonymous namespace
|
||||
|
||||
web_seed_entry::web_seed_entry(std::string const& url_, type_t type_
|
||||
, std::string const& auth_
|
||||
|
|
|
@ -683,8 +683,8 @@ TORRENT_TEST(parse_torrents)
|
|||
else if (std::string(test_torrents[i].file) == "pad_file.torrent")
|
||||
{
|
||||
TEST_EQUAL(ti->num_files(), 2);
|
||||
TEST_EQUAL(ti->files().file_flags(file_index_t{0}) & file_storage::flag_pad_file, false);
|
||||
TEST_EQUAL(ti->files().file_flags(file_index_t{1}) & file_storage::flag_pad_file, true);
|
||||
TEST_EQUAL(bool(ti->files().file_flags(file_index_t{0}) & file_storage::flag_pad_file), false);
|
||||
TEST_EQUAL(bool(ti->files().file_flags(file_index_t{1}) & file_storage::flag_pad_file), true);
|
||||
}
|
||||
else if (std::string(test_torrents[i].file) == "creation_date.torrent")
|
||||
{
|
||||
|
@ -776,7 +776,7 @@ TORRENT_TEST(parse_torrents)
|
|||
{
|
||||
piece_index_t const first = ti->map_file(i, 0, 0).piece;
|
||||
piece_index_t const last = ti->map_file(i, std::max(fs.file_size(i)-1, std::int64_t(0)), 0).piece;
|
||||
int const flags = fs.file_flags(i);
|
||||
file_flags_t const flags = fs.file_flags(i);
|
||||
sha1_hash const ih = fs.hash(i);
|
||||
std::printf(" %11" PRId64 " %c%c%c%c [ %4d, %4d ] %7u %s %s %s%s\n"
|
||||
, fs.file_size(i)
|
||||
|
|
Loading…
Reference in New Issue