Trying very hard not to miss calendar events
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Rewrite in Rust, first pass

Co-authored-by: Claude <noreply@anthropic.com>

+394 -270
-13
.clang-format
··· 1 - --- 2 - BasedOnStyle: LLVM 3 - IndentWidth: 4 4 - ColumnLimit: 100 5 - BreakBeforeBraces: Linux 6 - AllowShortIfStatementsOnASingleLine: false 7 - AllowShortFunctionsOnASingleLine: None 8 - AllowShortLoopsOnASingleLine: false 9 - IndentCaseLabels: false 10 - PointerAlignment: Right 11 - SpaceAfterCStyleCast: true 12 - AlignConsecutiveAssignments: false 13 - AlignConsecutiveDeclarations: false
+14 -56
.gitignore
··· 1 - # Created by https://www.toptal.com/developers/gitignore/api/c,ninja 2 - # Edit at https://www.toptal.com/developers/gitignore?templates=c,ninja 1 + # Created by https://www.toptal.com/developers/gitignore/api/rust 2 + # Edit at https://www.toptal.com/developers/gitignore?templates=rust 3 3 4 - ### C ### 5 - # Prerequisites 6 - *.d 7 - 8 - # Object files 9 - *.o 10 - *.ko 11 - *.obj 12 - *.elf 4 + ### Rust ### 5 + # Generated by Cargo 6 + # will have compiled files and executables 7 + debug/ 8 + target/ 13 9 14 - # Linker output 15 - *.ilk 16 - *.map 17 - *.exp 18 - 19 - # Precompiled Headers 20 - *.gch 21 - *.pch 22 - 23 - # Libraries 24 - *.lib 25 - *.a 26 - *.la 27 - *.lo 28 - 29 - # Shared objects (inc. Windows DLLs) 30 - *.dll 31 - *.so 32 - *.so.* 33 - *.dylib 10 + # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 11 + # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 12 + Cargo.lock 34 13 35 - # Executables 36 - *.exe 37 - *.out 38 - *.app 39 - *.i*86 40 - *.x86_64 41 - *.hex 14 + # These are backup files generated by rustfmt 15 + **/*.rs.bk 42 16 43 - # Debug files 44 - *.dSYM/ 45 - *.su 46 - *.idb 17 + # MSVC Windows builds of rustc generate these, which store debugging information 47 18 *.pdb 48 19 49 - # Kernel Module Compile Results 50 - *.mod* 51 - *.cmd 52 - .tmp_versions/ 53 - modules.order 54 - Module.symvers 55 - Mkfile.old 56 - dkms.conf 57 - 58 - ### Ninja ### 59 - .ninja_deps 60 - .ninja_log 61 - 62 - # End of https://www.toptal.com/developers/gitignore/api/c,ninja 20 + # End of https://www.toptal.com/developers/gitignore/api/rust 63 21 .vscode/
+16
Cargo.toml
··· 1 + [package] 2 + name = "list_events_rs" 3 + version = "0.1.0" 4 + edition = "2021" 5 + 6 + [[bin]] 7 + name = "list_events" 8 + path = "src/main.rs" 9 + 10 + [dependencies] 11 + glib-sys = "0.20" 12 + gio-sys = "0.20" 13 + libc = "0.2" 14 + 15 + [build-dependencies] 16 + pkg-config = "0.3"
-20
build.ninja
··· 1 - rule cc 2 - command = gcc -c $cflags $in -o $out 3 - description = Compiling $in 4 - 5 - rule link 6 - command = gcc $in $ldflags -o $out 7 - description = Linking $out 8 - 9 - rule format 10 - command = clang-format -i $in 11 - description = Formatting $in 12 - 13 - cflags = -Wall -Wextra `pkg-config --cflags libedataserver-1.2 libecal-2.0 libsoup-3.0 glib-2.0` 14 - ldflags = `pkg-config --libs libedataserver-1.2 libecal-2.0 libsoup-3.0 glib-2.0` 15 - 16 - build list_events.o: cc list_events.c 17 - 18 - build list_events.out: link list_events.o 19 - 20 - build format: format list_events.c
+12
build.rs
··· 1 + fn main() { 2 + // Link against the Evolution Data Server libraries 3 + pkg_config::Config::new() 4 + .probe("libedataserver-1.2") 5 + .unwrap(); 6 + pkg_config::Config::new() 7 + .probe("libecal-2.0") 8 + .unwrap(); 9 + pkg_config::Config::new() 10 + .probe("glib-2.0") 11 + .unwrap(); 12 + }
-181
list_events.c
··· 1 - #include <glib.h> 2 - #include <libecal/libecal.h> 3 - #include <libedataserver/libedataserver.h> 4 - #include <libical-glib/libical-glib.h> 5 - #include <stdio.h> 6 - #include <string.h> 7 - #include <time.h> 8 - 9 - void print_version() 10 - { 11 - printf("Evolution Data Server test program\n"); 12 - printf("Library version info:\n"); 13 - printf(" GLib version: %d.%d.%d\n", glib_major_version, glib_minor_version, 14 - glib_micro_version); 15 - } 16 - 17 - void print_usage(const char *program_name) 18 - { 19 - printf("Usage: %s [COMMAND]\n", program_name); 20 - printf("\nCommands:\n"); 21 - printf(" --version Show version information\n"); 22 - printf(" sources list List available calendar sources\n"); 23 - printf(" events list List events from now to 30 days ahead\n"); 24 - } 25 - 26 - int list_sources() 27 - { 28 - GError *error = NULL; 29 - ESourceRegistry *registry = e_source_registry_new_sync(NULL, &error); 30 - 31 - if (error) { 32 - fprintf(stderr, "Failed to create source registry: %s\n", error->message); 33 - g_error_free(error); 34 - return 1; 35 - } 36 - 37 - if (registry) { 38 - printf("Successfully connected to Evolution Data Server\n"); 39 - 40 - // List available sources 41 - GList *sources = e_source_registry_list_sources(registry, E_SOURCE_EXTENSION_CALENDAR); 42 - printf("Found %d calendar source(s)\n", g_list_length(sources)); 43 - 44 - for (GList *l = sources; l != NULL; l = l->next) { 45 - ESource *source = E_SOURCE(l->data); 46 - const char *display_name = e_source_get_display_name(source); 47 - const char *uid = e_source_get_uid(source); 48 - printf(" - %s (UID: %s)\n", display_name, uid); 49 - } 50 - 51 - g_list_free_full(sources, g_object_unref); 52 - g_object_unref(registry); 53 - } 54 - 55 - return 0; 56 - } 57 - 58 - int list_events() 59 - { 60 - GError *error = NULL; 61 - ESourceRegistry *registry = e_source_registry_new_sync(NULL, &error); 62 - 63 - if (error) { 64 - fprintf(stderr, "Failed to create source registry: %s\n", error->message); 65 - g_error_free(error); 66 - return 1; 67 - } 68 - 69 - // Calculate time range: now to 30 days ahead 70 - time_t now = time(NULL); 71 - time_t end_time = now + (60 * 24 * 60 * 60); 72 - 73 - // Get all calendar sources 74 - GList *sources = e_source_registry_list_sources(registry, E_SOURCE_EXTENSION_CALENDAR); 75 - int total_events = 0; 76 - 77 - for (GList *l = sources; l != NULL; l = l->next) { 78 - ESource *source = E_SOURCE(l->data); 79 - const char *display_name = e_source_get_display_name(source); 80 - 81 - // Create calendar client 82 - EClient *client_base = 83 - e_cal_client_connect_sync(source, E_CAL_CLIENT_SOURCE_TYPE_EVENTS, 30, NULL, &error); 84 - 85 - if (error) { 86 - fprintf(stderr, "Failed to connect to calendar '%s': %s\n", display_name, 87 - error->message); 88 - g_clear_error(&error); 89 - continue; 90 - } 91 - 92 - ECalClient *client = E_CAL_CLIENT(client_base); 93 - 94 - // Query for events in the time range 95 - char *iso_start = isodate_from_time_t(now); 96 - char *iso_end = isodate_from_time_t(end_time); 97 - char *query = g_strdup_printf( 98 - "(occur-in-time-range? (make-time \"%s\") (make-time \"%s\"))", iso_start, iso_end); 99 - 100 - GSList *ical_components = NULL; 101 - gboolean success = 102 - e_cal_client_get_object_list_sync(client, query, &ical_components, NULL, &error); 103 - 104 - if (error) { 105 - fprintf(stderr, "Failed to query events from '%s': %s\n", display_name, error->message); 106 - g_clear_error(&error); 107 - g_free(query); 108 - g_free(iso_start); 109 - g_free(iso_end); 110 - g_object_unref(client_base); 111 - continue; 112 - } 113 - 114 - if (success && ical_components) { 115 - for (GSList *comp = ical_components; comp != NULL; comp = comp->next) { 116 - ICalComponent *icalcomp = (ICalComponent *) comp->data; 117 - 118 - if (!icalcomp) { 119 - continue; 120 - } 121 - 122 - const char *summary = i_cal_component_get_summary(icalcomp); 123 - 124 - ICalTime *start = i_cal_component_get_dtstart(icalcomp); 125 - ICalTime *end = i_cal_component_get_dtend(icalcomp); 126 - 127 - time_t start_t = i_cal_time_as_timet(start); 128 - time_t end_t = i_cal_time_as_timet(end); 129 - 130 - char start_buf[64], end_buf[64]; 131 - strftime(start_buf, sizeof(start_buf), "%Y-%m-%d %H:%M", localtime(&start_t)); 132 - strftime(end_buf, sizeof(end_buf), "%Y-%m-%d %H:%M", localtime(&end_t)); 133 - 134 - printf("[%s] %s | %s - %s\n", display_name, summary ? summary : "(No title)", 135 - start_buf, end_buf); 136 - total_events++; 137 - 138 - g_object_unref(start); 139 - g_object_unref(end); 140 - } 141 - 142 - g_slist_free_full(ical_components, g_object_unref); 143 - } 144 - 145 - g_free(query); 146 - g_free(iso_start); 147 - g_free(iso_end); 148 - g_object_unref(client_base); 149 - } 150 - 151 - printf("\nTotal events: %d\n", total_events); 152 - g_list_free_full(sources, g_object_unref); 153 - g_object_unref(registry); 154 - 155 - return 0; 156 - } 157 - 158 - int main(int argc, char *argv[]) 159 - { 160 - if (argc < 2) { 161 - print_usage(argv[0]); 162 - return 1; 163 - } 164 - 165 - if (strcmp(argv[1], "--version") == 0) { 166 - print_version(); 167 - return 0; 168 - } 169 - 170 - if (argc >= 3 && strcmp(argv[1], "sources") == 0 && strcmp(argv[2], "list") == 0) { 171 - return list_sources(); 172 - } 173 - 174 - if (argc >= 3 && strcmp(argv[1], "events") == 0 && strcmp(argv[2], "list") == 0) { 175 - return list_events(); 176 - } 177 - 178 - fprintf(stderr, "Unknown command\n\n"); 179 - print_usage(argv[0]); 180 - return 1; 181 - }
+352
src/main.rs
··· 1 + use std::env; 2 + use std::ffi::{CStr, CString}; 3 + use std::os::raw::{c_char, c_int, c_void}; 4 + use std::ptr; 5 + 6 + // FFI bindings for GLib 7 + #[link(name = "glib-2.0")] 8 + extern "C" { 9 + fn g_list_length(list: *mut GList) -> u32; 10 + fn g_list_free_full(list: *mut GList, free_func: Option<extern "C" fn(*mut c_void)>); 11 + fn g_slist_free_full(list: *mut GSList, free_func: Option<extern "C" fn(*mut c_void)>); 12 + fn g_object_unref(object: *mut c_void); 13 + fn g_error_free(error: *mut GError); 14 + fn g_clear_error(error: *mut *mut GError); 15 + fn g_strdup_printf(format: *const c_char, ...) -> *mut c_char; 16 + fn g_free(mem: *mut c_void); 17 + 18 + static glib_major_version: u32; 19 + static glib_minor_version: u32; 20 + static glib_micro_version: u32; 21 + } 22 + 23 + // FFI bindings for Evolution Data Server 24 + #[link(name = "edataserver-1.2")] 25 + extern "C" { 26 + fn e_source_registry_new_sync( 27 + cancellable: *mut c_void, 28 + error: *mut *mut GError, 29 + ) -> *mut ESourceRegistry; 30 + fn e_source_registry_list_sources( 31 + registry: *mut ESourceRegistry, 32 + extension_name: *const c_char, 33 + ) -> *mut GList; 34 + fn e_source_get_display_name(source: *mut ESource) -> *const c_char; 35 + fn e_source_get_uid(source: *mut ESource) -> *const c_char; 36 + } 37 + 38 + const E_SOURCE_EXTENSION_CALENDAR: &[u8] = b"Calendar\0"; 39 + 40 + // FFI bindings for Evolution Calendar 41 + #[link(name = "ecal-2.0")] 42 + extern "C" { 43 + fn e_cal_client_connect_sync( 44 + source: *mut ESource, 45 + source_type: ECalClientSourceType, 46 + wait_for_connected_seconds: u32, 47 + cancellable: *mut c_void, 48 + error: *mut *mut GError, 49 + ) -> *mut EClient; 50 + fn e_cal_client_get_object_list_sync( 51 + client: *mut ECalClient, 52 + sexp: *const c_char, 53 + out_icalcomps: *mut *mut GSList, 54 + cancellable: *mut c_void, 55 + error: *mut *mut GError, 56 + ) -> c_int; 57 + } 58 + 59 + // FFI bindings for libical-glib 60 + #[link(name = "ical-glib")] 61 + extern "C" { 62 + fn i_cal_component_get_summary(comp: *mut ICalComponent) -> *const c_char; 63 + fn i_cal_component_get_dtstart(comp: *mut ICalComponent) -> *mut ICalTime; 64 + fn i_cal_component_get_dtend(comp: *mut ICalComponent) -> *mut ICalTime; 65 + fn i_cal_time_as_timet(tt: *mut ICalTime) -> libc::time_t; 66 + } 67 + 68 + // FFI bindings for e-cal-time-util 69 + #[link(name = "ecal-2.0")] 70 + extern "C" { 71 + fn isodate_from_time_t(t: libc::time_t) -> *mut c_char; 72 + } 73 + 74 + // Opaque types 75 + #[repr(C)] 76 + struct GError { 77 + domain: u32, 78 + code: c_int, 79 + message: *mut c_char, 80 + } 81 + 82 + #[repr(C)] 83 + struct GList { 84 + data: *mut c_void, 85 + next: *mut GList, 86 + prev: *mut GList, 87 + } 88 + 89 + #[repr(C)] 90 + struct GSList { 91 + data: *mut c_void, 92 + next: *mut GSList, 93 + } 94 + 95 + enum ESourceRegistry {} 96 + enum ESource {} 97 + enum EClient {} 98 + enum ECalClient {} 99 + enum ICalComponent {} 100 + enum ICalTime {} 101 + 102 + #[repr(C)] 103 + enum ECalClientSourceType { 104 + Events = 0, 105 + } 106 + 107 + extern "C" fn glib_object_unref_wrapper(ptr: *mut c_void) { 108 + unsafe { g_object_unref(ptr) } 109 + } 110 + 111 + fn print_version() { 112 + unsafe { 113 + println!("Evolution Data Server test program"); 114 + println!("Library version info:"); 115 + println!(" GLib version: {}.{}.{}", 116 + glib_major_version, 117 + glib_minor_version, 118 + glib_micro_version 119 + ); 120 + } 121 + } 122 + 123 + fn print_usage(program_name: &str) { 124 + println!("Usage: {} [COMMAND]", program_name); 125 + println!("\nCommands:"); 126 + println!(" --version Show version information"); 127 + println!(" sources list List available calendar sources"); 128 + println!(" events list List events from now to 60 days ahead"); 129 + } 130 + 131 + fn list_sources() -> i32 { 132 + unsafe { 133 + let mut error: *mut GError = ptr::null_mut(); 134 + let registry = e_source_registry_new_sync(ptr::null_mut(), &mut error); 135 + 136 + if !error.is_null() { 137 + let err_msg = CStr::from_ptr((*error).message); 138 + eprintln!("Failed to create source registry: {}", err_msg.to_string_lossy()); 139 + g_error_free(error); 140 + return 1; 141 + } 142 + 143 + if !registry.is_null() { 144 + println!("Successfully connected to Evolution Data Server"); 145 + 146 + let sources = e_source_registry_list_sources( 147 + registry, 148 + E_SOURCE_EXTENSION_CALENDAR.as_ptr() as *const c_char 149 + ); 150 + let count = g_list_length(sources); 151 + println!("Found {} calendar source(s)", count); 152 + 153 + let mut current = sources; 154 + while !current.is_null() { 155 + let source = (*current).data as *mut ESource; 156 + let display_name = CStr::from_ptr(e_source_get_display_name(source)); 157 + let uid = CStr::from_ptr(e_source_get_uid(source)); 158 + println!(" - {} (UID: {})", 159 + display_name.to_string_lossy(), 160 + uid.to_string_lossy() 161 + ); 162 + current = (*current).next; 163 + } 164 + 165 + g_list_free_full(sources, Some(glib_object_unref_wrapper)); 166 + g_object_unref(registry as *mut c_void); 167 + } 168 + } 169 + 170 + 0 171 + } 172 + 173 + fn list_events() -> i32 { 174 + unsafe { 175 + let mut error: *mut GError = ptr::null_mut(); 176 + let registry = e_source_registry_new_sync(ptr::null_mut(), &mut error); 177 + 178 + if !error.is_null() { 179 + let err_msg = CStr::from_ptr((*error).message); 180 + eprintln!("Failed to create source registry: {}", err_msg.to_string_lossy()); 181 + g_error_free(error); 182 + return 1; 183 + } 184 + 185 + let now = libc::time(ptr::null_mut()); 186 + let end_time = now + (60 * 24 * 60 * 60); 187 + 188 + let sources = e_source_registry_list_sources( 189 + registry, 190 + E_SOURCE_EXTENSION_CALENDAR.as_ptr() as *const c_char 191 + ); 192 + let mut total_events = 0; 193 + 194 + let mut current_source = sources; 195 + while !current_source.is_null() { 196 + let source = (*current_source).data as *mut ESource; 197 + let display_name = CStr::from_ptr(e_source_get_display_name(source)); 198 + 199 + let client_base = e_cal_client_connect_sync( 200 + source, 201 + ECalClientSourceType::Events, 202 + 30, 203 + ptr::null_mut(), 204 + &mut error, 205 + ); 206 + 207 + if !error.is_null() { 208 + let err_msg = CStr::from_ptr((*error).message); 209 + eprintln!( 210 + "Failed to connect to calendar '{}': {}", 211 + display_name.to_string_lossy(), 212 + err_msg.to_string_lossy() 213 + ); 214 + g_clear_error(&mut error); 215 + current_source = (*current_source).next; 216 + continue; 217 + } 218 + 219 + let client = client_base as *mut ECalClient; 220 + 221 + let iso_start = isodate_from_time_t(now); 222 + let iso_end = isodate_from_time_t(end_time); 223 + let query_fmt = CString::new("(occur-in-time-range? (make-time \"%s\") (make-time \"%s\"))").unwrap(); 224 + let query = g_strdup_printf(query_fmt.as_ptr(), iso_start, iso_end); 225 + 226 + let mut ical_components: *mut GSList = ptr::null_mut(); 227 + let success = e_cal_client_get_object_list_sync( 228 + client, 229 + query, 230 + &mut ical_components, 231 + ptr::null_mut(), 232 + &mut error, 233 + ); 234 + 235 + if !error.is_null() { 236 + let err_msg = CStr::from_ptr((*error).message); 237 + eprintln!( 238 + "Failed to query events from '{}': {}", 239 + display_name.to_string_lossy(), 240 + err_msg.to_string_lossy() 241 + ); 242 + g_clear_error(&mut error); 243 + g_free(query as *mut c_void); 244 + g_free(iso_start as *mut c_void); 245 + g_free(iso_end as *mut c_void); 246 + g_object_unref(client_base as *mut c_void); 247 + current_source = (*current_source).next; 248 + continue; 249 + } 250 + 251 + if success != 0 && !ical_components.is_null() { 252 + let mut current_comp = ical_components; 253 + while !current_comp.is_null() { 254 + let icalcomp = (*current_comp).data as *mut ICalComponent; 255 + 256 + if !icalcomp.is_null() { 257 + let summary_ptr = i_cal_component_get_summary(icalcomp); 258 + let summary = if !summary_ptr.is_null() { 259 + CStr::from_ptr(summary_ptr).to_string_lossy() 260 + } else { 261 + "(No title)".into() 262 + }; 263 + 264 + let start = i_cal_component_get_dtstart(icalcomp); 265 + let end = i_cal_component_get_dtend(icalcomp); 266 + 267 + let start_t = i_cal_time_as_timet(start); 268 + let end_t = i_cal_time_as_timet(end); 269 + 270 + let mut start_buf = vec![0u8; 64]; 271 + let mut end_buf = vec![0u8; 64]; 272 + let fmt = CString::new("%Y-%m-%d %H:%M").unwrap(); 273 + 274 + let start_tm = libc::localtime(&start_t); 275 + libc::strftime( 276 + start_buf.as_mut_ptr() as *mut c_char, 277 + start_buf.len(), 278 + fmt.as_ptr(), 279 + start_tm, 280 + ); 281 + 282 + let end_tm = libc::localtime(&end_t); 283 + libc::strftime( 284 + end_buf.as_mut_ptr() as *mut c_char, 285 + end_buf.len(), 286 + fmt.as_ptr(), 287 + end_tm, 288 + ); 289 + 290 + let start_str = CStr::from_ptr(start_buf.as_ptr() as *const c_char) 291 + .to_string_lossy(); 292 + let end_str = CStr::from_ptr(end_buf.as_ptr() as *const c_char) 293 + .to_string_lossy(); 294 + 295 + println!( 296 + "[{}] {} | {} - {}", 297 + display_name.to_string_lossy(), 298 + summary, 299 + start_str, 300 + end_str 301 + ); 302 + total_events += 1; 303 + 304 + g_object_unref(start as *mut c_void); 305 + g_object_unref(end as *mut c_void); 306 + } 307 + 308 + current_comp = (*current_comp).next; 309 + } 310 + 311 + g_slist_free_full(ical_components, Some(glib_object_unref_wrapper)); 312 + } 313 + 314 + g_free(query as *mut c_void); 315 + g_free(iso_start as *mut c_void); 316 + g_free(iso_end as *mut c_void); 317 + g_object_unref(client_base as *mut c_void); 318 + 319 + current_source = (*current_source).next; 320 + } 321 + 322 + println!("\nTotal events: {}", total_events); 323 + g_list_free_full(sources, Some(glib_object_unref_wrapper)); 324 + g_object_unref(registry as *mut c_void); 325 + } 326 + 327 + 0 328 + } 329 + 330 + fn main() { 331 + let args: Vec<String> = env::args().collect(); 332 + 333 + if args.len() < 2 { 334 + print_usage(&args[0]); 335 + std::process::exit(1); 336 + } 337 + 338 + let exit_code = if args[1] == "--version" { 339 + print_version(); 340 + 0 341 + } else if args.len() >= 3 && args[1] == "sources" && args[2] == "list" { 342 + list_sources() 343 + } else if args.len() >= 3 && args[1] == "events" && args[2] == "list" { 344 + list_events() 345 + } else { 346 + eprintln!("Unknown command\n"); 347 + print_usage(&args[0]); 348 + 1 349 + }; 350 + 351 + std::process::exit(exit_code); 352 + }