summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibCore/FileWatcher.h
blob: 694e9f485bdc72781e27ee37066e0ada45621d98 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/*
 * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
 * Copyright (c) 2021-2022, the SerenityOS developers.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#pragma once

#include <AK/DeprecatedString.h>
#include <AK/EnumBits.h>
#include <AK/Function.h>
#include <AK/Noncopyable.h>
#include <AK/NonnullRefPtr.h>
#include <AK/RefCounted.h>
#include <LibCore/Notifier.h>

namespace Core {

struct FileWatcherEvent {
    enum class Type {
        Invalid = 0,
        MetadataModified = 1 << 0,
        ContentModified = 1 << 1,
        Deleted = 1 << 2,
        ChildCreated = 1 << 3,
        ChildDeleted = 1 << 4,
    };
    Type type { Type::Invalid };
    DeprecatedString event_path;
};

AK_ENUM_BITWISE_OPERATORS(FileWatcherEvent::Type);

enum class FileWatcherFlags : u32 {
    None = 0,
    Nonblock = 1 << 0,
    CloseOnExec = 1 << 1,
};

AK_ENUM_BITWISE_OPERATORS(FileWatcherFlags);

class FileWatcherBase {
public:
    virtual ~FileWatcherBase() = default;

    ErrorOr<bool> add_watch(DeprecatedString path, FileWatcherEvent::Type event_mask);
    ErrorOr<bool> remove_watch(DeprecatedString path);
    bool is_watching(DeprecatedString const& path) const { return m_path_to_wd.find(path) != m_path_to_wd.end(); }

protected:
    FileWatcherBase(int watcher_fd)
        : m_watcher_fd(watcher_fd)
    {
    }

    int m_watcher_fd { -1 };
    HashMap<DeprecatedString, unsigned> m_path_to_wd;
    HashMap<unsigned, DeprecatedString> m_wd_to_path;
};

class BlockingFileWatcher final : public FileWatcherBase {
    AK_MAKE_NONCOPYABLE(BlockingFileWatcher);

public:
    explicit BlockingFileWatcher(FileWatcherFlags = FileWatcherFlags::None);
    ~BlockingFileWatcher();

    Optional<FileWatcherEvent> wait_for_event();
};

class FileWatcher : public FileWatcherBase
    , public RefCounted<FileWatcher> {
    AK_MAKE_NONCOPYABLE(FileWatcher);

public:
    static ErrorOr<NonnullRefPtr<FileWatcher>> create(FileWatcherFlags = FileWatcherFlags::None);
    ~FileWatcher();

    Function<void(FileWatcherEvent const&)> on_change;

protected:
    FileWatcher(int watcher_fd, NonnullRefPtr<Notifier>);

    NonnullRefPtr<Notifier> m_notifier;
};

}

namespace AK {

template<>
struct Formatter<Core::FileWatcherEvent> : Formatter<FormatString> {
    ErrorOr<void> format(FormatBuilder& builder, Core::FileWatcherEvent const& value)
    {
        return Formatter<FormatString>::format(builder, "FileWatcherEvent(\"{}\", {})"sv, value.event_path, value.type);
    }
};

template<>
struct Formatter<Core::FileWatcherEvent::Type> : Formatter<FormatString> {
    ErrorOr<void> format(FormatBuilder& builder, Core::FileWatcherEvent::Type const& value)
    {
        bool had_any_flag = false;

        auto put_string_if_has_flag = [&](auto mask, auto name) -> ErrorOr<void> {
            if (!has_flag(value, mask))
                return {};

            if (had_any_flag)
                TRY(builder.put_string(", "sv));
            TRY(builder.put_string(name));

            had_any_flag = true;
            return {};
        };

        TRY(builder.put_string("["sv));
        TRY(put_string_if_has_flag(Core::FileWatcherEvent::Type::ChildCreated, "ChildCreated"sv));
        TRY(put_string_if_has_flag(Core::FileWatcherEvent::Type::ChildDeleted, "ChildDeleted"sv));
        TRY(put_string_if_has_flag(Core::FileWatcherEvent::Type::Deleted, "Deleted"sv));
        TRY(put_string_if_has_flag(Core::FileWatcherEvent::Type::ContentModified, "ContentModified"sv));
        TRY(put_string_if_has_flag(Core::FileWatcherEvent::Type::MetadataModified, "MetadataModified"sv));
        TRY(builder.put_string("]"sv));

        VERIFY(had_any_flag);
        return {};
    }
};

}