1 |
root |
1.1 |
#! perl # mandatory |
2 |
|
|
|
3 |
root |
1.3 |
our $SCHEDULE_INTERVAL = $cf::CFG{extractor_schedule_interval} || 3600; |
4 |
root |
1.1 |
|
5 |
|
|
use JSON::XS; |
6 |
|
|
|
7 |
|
|
my $db_mapinfo = cf::sync_job { cf::db_table "tag-mapinfo" }; # info/cache for maps |
8 |
|
|
my $db_target = cf::sync_job { cf::db_table "tag-target" }; # tag => maps |
9 |
|
|
|
10 |
|
|
sub remove_tag_target { |
11 |
|
|
my ($txn, $tag, $target) = @_; |
12 |
|
|
# - U O |
13 |
|
|
|
14 |
|
|
utf8::encode $tag; |
15 |
|
|
BDB::db_get $db_target, $txn, $tag, my $data; |
16 |
|
|
my @tags = split /\x00/, $data; |
17 |
|
|
@tags = grep $_ ne $target, @tags; |
18 |
|
|
|
19 |
|
|
if (@tags) { |
20 |
|
|
BDB::db_put $db_target, $txn, $tag, join "\x00", @tags; |
21 |
|
|
} else { |
22 |
|
|
BDB::db_del $db_target, $txn, $tag; |
23 |
|
|
} |
24 |
|
|
} |
25 |
|
|
|
26 |
|
|
sub add_tag_target { |
27 |
|
|
my ($txn, $tag, $target) = @_; |
28 |
|
|
|
29 |
|
|
utf8::encode $tag; |
30 |
|
|
BDB::db_put $db_target, $txn, $tag, my $data; |
31 |
|
|
my @tags = split /\x00/, $data; |
32 |
|
|
push @tags, $target; |
33 |
|
|
BDB::db_put $db_target, $txn, $tag, join "\x00", @tags; |
34 |
|
|
} |
35 |
|
|
|
36 |
|
|
sub scan_map($$) { |
37 |
|
|
my ($key, $file) = @_; |
38 |
|
|
# unicode key, octets file |
39 |
|
|
|
40 |
|
|
Coro::AIO::aio_stat $file |
41 |
|
|
and next; |
42 |
|
|
|
43 |
|
|
my $hash = join ",", 1, (stat _)[7,9], $file; |
44 |
|
|
|
45 |
|
|
my $old_tags; |
46 |
|
|
|
47 |
|
|
my $txn = $cf::DB_ENV->txn_begin; |
48 |
|
|
|
49 |
|
|
utf8::encode $key; |
50 |
|
|
BDB::db_get $db_mapinfo, $txn, $key, my $data; |
51 |
|
|
|
52 |
|
|
unless ($!) { |
53 |
|
|
$data = from_json $data; |
54 |
|
|
return if $data->{hash} eq $hash; |
55 |
|
|
$old_tags = $data->{tags}; |
56 |
|
|
} |
57 |
|
|
|
58 |
|
|
$old_tags ||= []; |
59 |
|
|
|
60 |
|
|
my $f = new_from_file cf::object::thawer $file |
61 |
|
|
or return; |
62 |
|
|
|
63 |
|
|
my @tags = sort $f->extract_tags; |
64 |
|
|
$data = to_json { hash => $hash, tags => \@tags }; |
65 |
|
|
|
66 |
|
|
BDB::db_put $db_mapinfo, $txn, $key, $data; |
67 |
|
|
|
68 |
|
|
# 1. remove tags no longer existing |
69 |
|
|
for my $tag (@$old_tags) { |
70 |
|
|
next if grep $_ eq $tag, @tags; |
71 |
|
|
remove_tag_target $txn, $tag, $key; |
72 |
|
|
} |
73 |
|
|
|
74 |
|
|
# 2. add tags that are new |
75 |
|
|
for my $tag (@tags) { |
76 |
|
|
next if grep $_ eq $tag, @$old_tags; |
77 |
|
|
add_tag_target $txn, $tag, $key; |
78 |
|
|
} |
79 |
|
|
|
80 |
|
|
# we don't actually care if it succeeds or not, as we |
81 |
|
|
# will just retry an hour later |
82 |
root |
1.7 |
BDB::db_txn_finish $txn; |
83 |
root |
1.1 |
|
84 |
|
|
# warn "tag-updated $file (= $key) $hash\n";#d# |
85 |
|
|
} |
86 |
|
|
|
87 |
|
|
sub scan_static { |
88 |
|
|
my ($dir, $map) = @_; |
89 |
|
|
|
90 |
|
|
my ($dirs, $files) = Coro::AIO::aio_scandir $dir, 2 |
91 |
|
|
or return; |
92 |
|
|
|
93 |
|
|
for my $file (@$files) { |
94 |
|
|
my $name = $file; |
95 |
|
|
next unless $name =~ s/\.map$//; |
96 |
|
|
utf8::decode $name; |
97 |
|
|
|
98 |
|
|
scan_map "s$map$name", "$dir/$file"; |
99 |
|
|
} |
100 |
|
|
|
101 |
|
|
&scan_static ("$dir/$_", "$map$_/") |
102 |
|
|
for @$dirs; |
103 |
|
|
} |
104 |
|
|
|
105 |
root |
1.5 |
sub reload { |
106 |
|
|
my $start = Event::time; |
107 |
root |
1.1 |
|
108 |
root |
1.5 |
# 1. check for maps no longer existing |
109 |
root |
1.6 |
{ |
110 |
|
|
my @delkeys; |
111 |
|
|
|
112 |
|
|
my $cursor = $db_mapinfo->cursor; |
113 |
|
|
for (;;) { |
114 |
|
|
BDB::db_c_get $cursor, my $key, my $data, BDB::NEXT; |
115 |
|
|
last if $!; |
116 |
|
|
|
117 |
|
|
my $data = JSON::XS::from_json $data; |
118 |
|
|
my ($ver, undef, undef, $path) = split /,/, $data->{hash}, 4; |
119 |
|
|
push @delkeys, [$key, $data->{tags}] |
120 |
|
|
if $ver != 1 || Coro::AIO::aio_stat $path; |
121 |
|
|
} |
122 |
|
|
BDB::db_c_close $cursor; |
123 |
|
|
|
124 |
|
|
for (@delkeys) { |
125 |
|
|
my ($key, $tags) = @$_; |
126 |
|
|
my $txn = $cf::DB_ENV->txn_begin; |
127 |
|
|
BDB::db_del $db_mapinfo, $txn, $key; |
128 |
|
|
for my $tag (@{ $tags || [] }) { |
129 |
|
|
remove_tag_target $txn, $tag, $key; |
130 |
|
|
} |
131 |
root |
1.7 |
BDB::db_txn_finish $txn; |
132 |
root |
1.6 |
} |
133 |
|
|
} |
134 |
root |
1.1 |
|
135 |
root |
1.5 |
# 2. scan all static maps |
136 |
|
|
scan_static $cf::MAPDIR, "/"; |
137 |
root |
1.1 |
|
138 |
root |
1.5 |
# 3. scan all dynamic maps |
139 |
|
|
for my $path (@{ cf::map::tmp_maps or [] }, @{ cf::map::random_maps or [] }) { |
140 |
root |
1.1 |
# my $map = cf::map::find $path; |
141 |
|
|
# extract_map_tags "t/$map", $path; |
142 |
root |
1.5 |
} |
143 |
|
|
|
144 |
root |
1.1 |
# now hunt for all per-player maps |
145 |
|
|
# scan_dir $cf::PLAYERDIR |
146 |
|
|
# for my $login (@{ cf::player::list_logins or [] }) { |
147 |
|
|
# for my $path (@{ cf::player::maps $login or [] }) { |
148 |
|
|
# cf::cede_to_tick; |
149 |
|
|
# |
150 |
|
|
# $path =~ /^~[^\/]+(\/.*)$/ |
151 |
|
|
# or next; # doh |
152 |
|
|
# |
153 |
|
|
# my $base = cf::map::find $1; |
154 |
|
|
# |
155 |
|
|
# # skip maps without base maps on the assumption |
156 |
|
|
# # that those are old, unresettable maps |
157 |
|
|
# next unless $base; |
158 |
|
|
# |
159 |
|
|
# # skip unresettable maps, for speed |
160 |
|
|
# next if $base->{deny_reset}; |
161 |
|
|
# |
162 |
|
|
# my $map = cf::map::find $path; |
163 |
|
|
# |
164 |
|
|
# if ($map->{deny_reset}) { |
165 |
|
|
# warn "found noreset map with resettable base map, resetting: $path\n"; |
166 |
|
|
# delete $map->{deny_reset}; |
167 |
|
|
# } |
168 |
|
|
# } |
169 |
|
|
# } |
170 |
|
|
|
171 |
root |
1.5 |
warn sprintf "map-tag scan (%fs)", Event::time - $start; |
172 |
|
|
} |
173 |
|
|
|
174 |
|
|
our $RELOAD_SCHEDULER = Event->timer ( |
175 |
|
|
after => 0, |
176 |
|
|
interval => $SCHEDULE_INTERVAL, |
177 |
|
|
data => cf::WF_AUTOCANCEL, |
178 |
|
|
cb => Coro::unblock_sub { |
179 |
|
|
$Coro::current->prio (Coro::PRIO_MIN); |
180 |
|
|
reload; |
181 |
|
|
}, |
182 |
|
|
); |
183 |
root |
1.1 |
|
184 |
root |
1.5 |
# find all objects with the given tag, or at least try to |
185 |
root |
1.2 |
sub find($) { |
186 |
|
|
my ($tag) = @_; |
187 |
root |
1.1 |
|
188 |
root |
1.3 |
utf8::encode (my $key = $tag); |
189 |
|
|
BDB::db_get $db_target, undef, $key, my $data; |
190 |
root |
1.2 |
utf8::decode $data; |
191 |
|
|
|
192 |
root |
1.4 |
map { $_->load; $_->find_tagged_objects ($tag) } |
193 |
root |
1.2 |
grep $_, |
194 |
|
|
map { cf::map::find $_ } |
195 |
|
|
grep s/^s//, |
196 |
|
|
split /\x00/, $data |
197 |
|
|
} |