소스 검색

tail: Port to Core::Stream, use Core::FileWatcher

demostanis 2 년 전
부모
커밋
c8f790e4dd
1개의 변경된 파일94개의 추가작업 그리고 54개의 파일을 삭제
  1. 94 54
      Userland/Utilities/tail.cpp

+ 94 - 54
Userland/Utilities/tail.cpp

@@ -5,67 +5,41 @@
  */
 
 #include <LibCore/ArgsParser.h>
-#include <LibCore/File.h>
+#include <LibCore/EventLoop.h>
+#include <LibCore/FileWatcher.h>
+#include <LibCore/Stream.h>
 #include <LibCore/System.h>
-#include <LibMain/Main.h>
-#include <stdlib.h>
-#include <unistd.h>
 
 #define DEFAULT_LINE_COUNT 10
 
-static int tail_from_pos(Core::File& file, off_t startline, bool want_follow)
+static ErrorOr<void> tail_from_pos(Core::Stream::File& file, off_t startline)
 {
-    if (!file.seek(startline + 1))
-        return 1;
-
-    while (true) {
-        auto const& b = file.read(4096);
-        if (b.is_empty()) {
-            if (!want_follow) {
-                break;
-            } else {
-                while (!file.can_read()) {
-                    // FIXME: would be nice to have access to can_read_from_fd with an infinite timeout
-                    usleep(100);
-                }
-                continue;
-            }
-        }
-
-        if (write(STDOUT_FILENO, b.data(), b.size()) < 0)
-            return 1;
-    }
-
-    return 0;
+    TRY(file.seek(startline + 1, Core::Stream::SeekMode::SetPosition));
+    auto buffer = TRY(file.read_all());
+    out("{}", StringView { buffer });
+    return {};
 }
 
-static off_t find_seek_pos(Core::File& file, int wanted_lines)
+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 = 0;
-    if (!file.seek(0, Core::SeekMode::FromEndPosition, &pos)) {
-        warnln("Failed to find end of file: {}", file.error_string());
-        return 1;
-    }
+    off_t pos = TRY(file.seek(0, Core::Stream::SeekMode::FromEndPosition));
 
     off_t end = pos;
     int lines = 0;
 
-    // FIXME: Reading char-by-char is only OK if IODevice's read buffer
-    // is smart enough to not read char-by-char. Fix it there, or fix it here :)
     for (; pos >= 0; pos--) {
-        file.seek(pos);
-        auto const& ch = file.read(1);
-        if (ch.is_empty()) {
-            // Presumably the file got truncated?
-            // Keep trying to read backwards...
-        } else {
-            if (*ch.data() == '\n' && (end - pos) > 1) {
-                lines++;
-                if (lines == wanted_lines)
-                    break;
-            }
+        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;
         }
     }
 
@@ -77,19 +51,85 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
     TRY(Core::System::pledge("stdio rpath"));
 
     bool follow = false;
-    int line_count = DEFAULT_LINE_COUNT;
-    char const* file = nullptr;
+    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(line_count, "Fetch the specified number of lines", "lines", 'n', "number");
-    args_parser.add_positional_argument(file, "File path", "file");
+    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::File::open(file, Core::OpenMode::ReadOnly));
-    TRY(Core::System::pledge("stdio"));
+    auto f = TRY(Core::Stream::File::open_file_or_standard_stream(file, Core::Stream::OpenMode::Read));
+    if (!follow)
+        TRY(Core::System::pledge("stdio"));
 
-    auto pos = find_seek_pos(*f, line_count);
-    return tail_from_pos(*f, pos, follow);
+    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_all(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.build());
+                    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_all();
+                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;
 }