Only try for about 98% full
[backups/.git] / main.cpp
1 #include <iostream>
2 #include <fstream>
3 #include <iterator>
4 #include <algorithm>
5 #include <cassert>
6 #include <ctime>
7
8 #include "filedata.hpp"
9
10 using namespace std;
11
12 unsigned long long current_time() {
13   unsigned long long rc = 0;
14   time_t now_tt = time( 0 );
15   tm *now = localtime( &now_tt );
16   rc += ( now->tm_year + 1900ULL ) * 10000000000ULL;
17   rc += ( now->tm_mon  + 1ULL )    * 100000000ULL;
18   rc +=   now->tm_mday             * 1000000ULL;
19   rc +=   now->tm_hour             * 10000ULL;
20   rc +=   now->tm_min              * 100ULL;
21   rc +=   now->tm_sec;
22
23   return rc;
24 }
25
26 template<class I, class O>
27 bool copy_until_full( I begin, I end, O out, unsigned long long &space ) {
28   const unsigned long long block_size = 0x200ULL;
29   bool complete = true;
30
31   I i = begin;
32   while( 0 != space && i != end ) {
33     unsigned long long size = (*i)->getFileSize();
34     unsigned long long blocks = size & ( ~(block_size-1) );
35     if( blocks < size ) blocks += block_size;
36
37     if( blocks <= space ) {
38       space -= blocks;
39       out = *i;
40       ++out;
41     } else {
42       // We missed a file that should be included so the backup is not complete
43       complete = false;
44     }
45     ++i;
46   }
47   return complete;
48 }
49
50 template<class SET>
51 void populate_set( istream &in, SET &files ) {
52   do {
53     FileData *data = new FileData();
54     in >> data;
55     if( data->getFileName().size() ) {
56       files.insert( data );
57     } else {
58       delete data;
59     }
60   } while( ! in.eof() );
61 }
62
63 template<class SET>
64 void partition_sets( const SET &current, const SET &old,
65                      SET &added, SET &common, SET &old_common, SET &deleted  ) {
66   FileDataNameCmp cmp;
67
68   set_difference( current.begin(), current.end(),
69                   old.begin(),     old.end(),
70                   inserter( added, added.begin() ),
71                   cmp );
72
73   set_difference( old.begin(),     old.end(),
74                   current.begin(), current.end(),
75                   inserter( deleted, deleted.begin() ),
76                   cmp );
77
78   set_union(      current.begin(), current.end(),
79                   old.begin(),     old.end(),
80                   inserter( common, common.begin() ),
81                   cmp );
82
83   set_union(      old.begin(),    old.end(),
84                   common.begin(), common.end(),
85                   inserter( old_common, old_common.begin() ),
86                   cmp );
87 }
88
89 int main() {
90   // Parse the list of current files on stdin
91   file_set current;
92   populate_set( cin, current );
93
94   file_set backed_up;
95   ifstream db( "test.db" );
96   if( db && db.good() ) {
97     populate_set( db, backed_up );
98   }
99
100   // Now divide the two sets into three sets (added, deleted and common )
101   file_set added, deleted, common, old_common;
102   partition_sets( current, backed_up, added, common, old_common, deleted );
103
104   // Now find the list of files to backup.
105   file_set backups;
106
107   // backup all added files
108   copy( added.begin(), added.end(), inserter( backups, backups.begin() ) );
109
110   // Backup files that have been modified
111   file_set::iterator i = common.begin(), j = old_common.begin();
112   for( ; i != common.end(); ++i, ++j ) {
113     (*i)->setLastBackupDate( (*j)->getLastBackupDate() );
114
115     if( needs_backup( *j, *i ) ) backups.insert( *i );
116   }
117
118   // Now, sort the backups by filesize and build a list that'll fit on a DVD
119   file_vector backups_s;
120   copy( backups.begin(), backups.end(), back_inserter( backups_s ) );
121
122   FileDataSizeCmp sizecmp;
123   sort( backups_s.begin(), backups_s.end(), sizecmp );
124
125   file_set final;
126   unsigned long long space = 0x102800000ULL;  // about 98% of 4220 MBytes
127
128   insert_iterator<file_set> final_i( final, final.begin() );
129
130   // Copy files over until full or out of files
131   bool complete = copy_until_full( backups_s.rbegin(),
132                                    backups_s.rend(),
133                                    final_i,
134                                    space );
135
136   // Now, sort the non-backed-up list by last_backup_date and back-fill
137   if( 0 != space ) {
138     file_vector leftovers;
139     FileDataNameCmp cmp;
140     set_difference( current.begin(), current.end(),
141                     final.begin(),   final.end(),
142                     back_inserter( leftovers ),
143                     cmp );
144
145     FileDataLastBackupCmp lastbackupcmp;
146     sort( leftovers.begin(), leftovers.end(), lastbackupcmp );
147
148     copy_until_full( leftovers.begin(), leftovers.end(), final_i, space );
149   }
150
151   unsigned long long now = current_time();
152   for( file_set::iterator k = final.begin(); k != final.end(); ++k ) {
153     (*k)->setLastBackupDate( now );
154   }
155
156   // Write the 'current' list to the dbfile
157   ofstream dbout( "test.db" );
158   copy( current.begin(), current.end(), ostream_iterator<FileData*>( dbout, "" ) );
159
160   // Write the 'final' list to stdout
161   copy( final.begin(), final.end(), ostream_iterator<FileData*>( cout, "" ) );
162
163   if( ! complete ) { cerr << "incomplete" << endl; }
164
165   // Clean-up
166   for( file_set::iterator i = backed_up.begin(); i != backed_up.end(); ++i ) { delete *i; }
167   for( file_set::iterator i = current.begin();   i != current.end();   ++i ) { delete *i; }
168 }