mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-25 05:55:13 +00:00
Having an alias function that only wraps another one is silly, and keeping the more obvious name should flush out more uses of deprecated strings. No behavior change.
135 lines
4.7 KiB
C++
135 lines
4.7 KiB
C++
/*
|
|
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <LibCore/ArgsParser.h>
|
|
#include <LibCore/EventLoop.h>
|
|
#include <LibCore/FileWatcher.h>
|
|
#include <LibCore/Stream.h>
|
|
#include <LibCore/System.h>
|
|
|
|
#define DEFAULT_LINE_COUNT 10
|
|
|
|
static ErrorOr<void> tail_from_pos(Core::Stream::File& file, off_t startline)
|
|
{
|
|
TRY(file.seek(startline + 1, Core::Stream::SeekMode::SetPosition));
|
|
auto buffer = TRY(file.read_until_eof());
|
|
out("{}", StringView { buffer });
|
|
return {};
|
|
}
|
|
|
|
static ErrorOr<off_t> find_seek_pos(Core::Stream::File& file, int wanted_lines)
|
|
{
|
|
// Rather than reading the whole file, start at the end and work backwards,
|
|
// stopping when we've found the number of lines we want.
|
|
off_t pos = TRY(file.seek(0, Core::Stream::SeekMode::FromEndPosition));
|
|
|
|
off_t end = pos;
|
|
int lines = 0;
|
|
|
|
for (; pos >= 0; pos--) {
|
|
TRY(file.seek(pos, Core::Stream::SeekMode::SetPosition));
|
|
|
|
if (file.is_eof())
|
|
break;
|
|
Array<u8, 1> buffer;
|
|
auto ch = TRY(file.read(buffer));
|
|
if (*ch.data() == '\n' && (end - pos) > 1) {
|
|
lines++;
|
|
if (lines == wanted_lines)
|
|
break;
|
|
}
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|
{
|
|
TRY(Core::System::pledge("stdio rpath"));
|
|
|
|
bool follow = false;
|
|
size_t wanted_line_count = DEFAULT_LINE_COUNT;
|
|
StringView file;
|
|
|
|
Core::ArgsParser args_parser;
|
|
args_parser.set_general_help("Print the end ('tail') of a file.");
|
|
args_parser.add_option(follow, "Output data as it is written to the file", "follow", 'f');
|
|
args_parser.add_option(wanted_line_count, "Fetch the specified number of lines", "lines", 'n', "number");
|
|
args_parser.add_positional_argument(file, "File path", "file", Core::ArgsParser::Required::No);
|
|
args_parser.parse(arguments);
|
|
|
|
auto f = TRY(Core::Stream::File::open_file_or_standard_stream(file, Core::Stream::OpenMode::Read));
|
|
if (!follow)
|
|
TRY(Core::System::pledge("stdio"));
|
|
|
|
auto file_is_seekable = !f->tell().is_error();
|
|
if (!file_is_seekable) {
|
|
do {
|
|
// FIXME: If f is the standard input, f->read_all() does not block
|
|
// anymore after sending EOF (^D), despite f->is_open() returning true.
|
|
auto buffer = TRY(f->read_until_eof(PAGE_SIZE));
|
|
auto line_count = StringView(buffer).count("\n"sv);
|
|
auto bytes = buffer.bytes();
|
|
size_t line_index = 0;
|
|
StringBuilder line;
|
|
|
|
if (!line_count && wanted_line_count) {
|
|
out("{}", StringView { bytes });
|
|
continue;
|
|
}
|
|
|
|
for (size_t i = 0; i < bytes.size(); i++) {
|
|
auto ch = bytes.at(i);
|
|
line.append(ch);
|
|
if (ch == '\n') {
|
|
if (wanted_line_count > line_count || line_index >= line_count - wanted_line_count)
|
|
out("{}", line.to_deprecated_string());
|
|
line_index++;
|
|
line.clear();
|
|
}
|
|
}
|
|
|
|
// Since we can't have FileWatchers on the standard input either,
|
|
// we just loop forever if the -f option was passed.
|
|
} while (follow);
|
|
return 0;
|
|
}
|
|
|
|
auto pos = TRY(find_seek_pos(*f, wanted_line_count));
|
|
TRY(tail_from_pos(*f, pos));
|
|
|
|
if (follow) {
|
|
TRY(f->seek(0, Core::Stream::SeekMode::FromEndPosition));
|
|
|
|
Core::EventLoop event_loop;
|
|
auto watcher = TRY(Core::FileWatcher::create());
|
|
watcher->on_change = [&](Core::FileWatcherEvent const& event) {
|
|
if (event.type == Core::FileWatcherEvent::Type::ContentModified) {
|
|
auto buffer_or_error = f->read_until_eof();
|
|
if (buffer_or_error.is_error()) {
|
|
auto error = buffer_or_error.error();
|
|
warnln(error.string_literal());
|
|
event_loop.quit(error.code());
|
|
return;
|
|
}
|
|
auto bytes = buffer_or_error.value().bytes();
|
|
out("{}", StringView { bytes });
|
|
|
|
auto potential_error = f->seek(0, Core::Stream::SeekMode::FromEndPosition);
|
|
if (potential_error.is_error()) {
|
|
auto error = potential_error.error();
|
|
warnln(error.string_literal());
|
|
event_loop.quit(error.code());
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
TRY(watcher->add_watch(file, Core::FileWatcherEvent::Type::ContentModified));
|
|
TRY(Core::System::pledge("stdio"));
|
|
return event_loop.exec();
|
|
}
|
|
return 0;
|
|
}
|