/* SOGoToolTruncateCalendar.m - this file is part of SOGo * * Copyright (C) 2009-2020 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #import #import #import #import #import #import #import #import #import "SOGoTool.h" @interface SOGoToolTruncateCalendar : SOGoTool { BOOL recurrent; } @end @implementation SOGoToolTruncateCalendar + (NSString *) command { return @"truncate-calendar"; } + (NSString *) description { return @"remove old calendar entries from the specified user calendar"; } - (BOOL) parseArguments { BOOL rc; int max; rc = NO; max = [arguments count]; if (max > 2) { recurrent = [[arguments objectAtIndex: 0] isEqualToString: @"-r"]; if (recurrent) { arguments = RETAIN([arguments subarrayWithRange: NSMakeRange(1, max-1)]); max--; } if (max == 3) rc = YES; } if (!rc) [self usage]; return rc; } - (void) removeRecord: (NSString *) recordName fromTable: (NSString *) tableName andQuickTable: (NSString *) quickTableName usingChannel: (EOAdaptorChannel *) channel { NSString *delSql; NSCalendarDate *now; /* We remove the records without regards to c_deleted because we really want to recover table space. */ now = [NSCalendarDate date]; delSql = [NSString stringWithFormat: @"UPDATE %@" @" SET c_deleted = 1, c_lastmodified = %lu," @" c_content = ''" @" WHERE c_name = '%@'", tableName, (NSUInteger) [now timeIntervalSince1970], recordName]; [channel evaluateExpressionX: delSql]; delSql = [NSString stringWithFormat: @"DELETE FROM %@" @" WHERE c_name = '%@'", quickTableName, recordName]; [channel evaluateExpressionX: delSql]; } - (void) removeRecords: (NSArray *) recordNames fromFolder: (GCSFolder *) folder { NSString *tableName, *quickTableName, *currentRecordName; NSDictionary *currentRecord; EOAdaptorChannel *channel; EOAdaptorContext *context; NSEnumerator *recordsEnum; fprintf (stderr, #if GS_64BIT_OLD "Removing %d records...\n", #else "Removing %ld records...\n", #endif [recordNames count]); channel = [folder acquireStoreChannel]; context = [channel adaptorContext]; [context beginTransaction]; tableName = [folder storeTableName]; quickTableName = [folder quickTableName]; recordsEnum = [recordNames objectEnumerator]; while ((currentRecord = [recordsEnum nextObject])) { currentRecordName = [currentRecord objectForKey: @"c_name"]; [self removeRecord: currentRecordName fromTable: tableName andQuickTable: quickTableName usingChannel: channel]; } [context commitTransaction]; [folder releaseChannel: channel immediately: YES]; } - (BOOL) truncateEntriesFromFolder: (GCSFolder *) folder usingDate: (NSCalendarDate *) date { NSMutableArray *records; EOQualifier *qualifier; NSArray *fields; NSString *qs; BOOL rc; records = [NSMutableArray array]; fields = [NSArray arrayWithObjects: @"c_name", nil]; if (recurrent) { // We fetch all events, including recurrent qs = [NSString stringWithFormat: @"c_enddate <= %d AND c_component == 'vevent'", (int)[date timeIntervalSince1970]]; qualifier = [EOQualifier qualifierWithQualifierFormat: qs]; [records addObjectsFromArray: [folder fetchFields: fields matchingQualifier: qualifier]]; } else { // We fetch non-repetitive events qs = [NSString stringWithFormat: @"c_enddate <= %d AND c_iscycle == 0 AND c_component == 'vevent'", (int)[date timeIntervalSince1970]]; qualifier = [EOQualifier qualifierWithQualifierFormat: qs]; [records addObjectsFromArray: [folder fetchFields: fields matchingQualifier: qualifier]]; // We fetch repetitive events with a cycle end date qs = [NSString stringWithFormat: @"c_cycleenddate <= %d AND c_iscycle == 1 AND c_component == 'vevent'", (int)[date timeIntervalSince1970]]; qualifier = [EOQualifier qualifierWithQualifierFormat: qs]; [records addObjectsFromArray: [folder fetchFields: fields matchingQualifier: qualifier]]; } if (records) { rc = YES; if ([records count]) { [self removeRecords: records fromFolder: folder]; fprintf (stderr, #if GS_64BIT_OLD "Removed %d records.\n", #else "Removed %ld records.\n", #endif [records count]); } else fprintf (stderr, "No record to remove. All records kept.\n"); } else { fprintf (stderr, "Unable to fetch required fields from folder.\n"); rc = NO; } return rc; } - (BOOL) processFolder: (NSString *) folderId ofUser: (NSString *) username date: (NSCalendarDate *) date withFoM: (GCSFolderManager *) fom { NSString *folderPath; GCSFolder *folder; BOOL rc; folderPath = [NSString stringWithFormat: @"/Users/%@/Calendar/%@", username, folderId]; folder = [fom folderAtPath: folderPath]; if (folder) rc = [self truncateEntriesFromFolder: folder usingDate: date]; else { fprintf (stderr, "Folder '%s' of user '%s' not found.\n", [folderId UTF8String], [username UTF8String]); rc = NO; } return rc; } - (BOOL) runWithFolder: (NSString *) folder andUser: (NSString *) username date: (NSString *) date { GCSFolderManager *fom; NSCalendarDate *d; NSString *s; BOOL rc; // We force parsing in the GMT timezone. If we don't do that, the date will be parsed // in the default timezone. s = [NSString stringWithFormat: @"%@ GMT", date]; d = [NSCalendarDate dateWithString: s calendarFormat: @"%Y-%m-%dT%H:%M:%S %Z"]; fom = [GCSFolderManager defaultFolderManager]; if (d && fom) rc = [self processFolder: folder ofUser: username date: d withFoM: fom]; else rc = NO; return rc; } - (void) usage { fprintf (stderr, "Usage: truncate-calendar [-r] USER FOLDER DATE\n\n" " -r also delete recurrent events (including all occurences)\n" " USER the owner of the calendar folder\n" " FOLDER the id of the folder to clean up\n" " DATE UTC datetime - events older than this date will be removed\n\n" "Examples: sogo-tool truncate-calendar bob personal 2016-06-27T17:38:56\n" " sogo-tool truncate-calendar -r bob personal `date +%%FT%%T` # deletes all events\n\n"); } - (BOOL) run { return ([self parseArguments] && [self runWithFolder: [arguments objectAtIndex: 1] andUser: [arguments objectAtIndex: 0] date: [arguments objectAtIndex: 2]]); } @end