Version 0.1.
[ccp.git] / ccp.c
1 /**
2  * ccp.c - a cp replacement with progress bar
3  *
4  * @version $Id: ccp.c 769 2008-04-26 10:53:11Z bombe $
5  */
6
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <sys/types.h>
10 #include <sys/stat.h>
11 #include <sys/ioctl.h>
12 #include <termios.h>
13 #include <unistd.h>
14 #include <errno.h>
15 #include <string.h>
16 #include <signal.h>
17 #include <sys/timeb.h>
18 #include <time.h>
19
20 #define false 0
21 #define true !false
22
23 int redrawProgressBar = false;
24
25 void handleWindowResize(int signalNumber) {
26         signal(signalNumber, SIG_IGN);
27
28         redrawProgressBar = true;
29
30         signal(signalNumber, handleWindowResize);
31 }
32
33 int getColumns() {
34         struct winsize terminalSize;
35         int error;
36
37         error = ioctl(1, TIOCGWINSZ, &terminalSize);
38         
39         if (!error) {
40                 return terminalSize.ws_col;
41         }
42         return 80;
43 }
44
45 char *extractFilename(char *pathName) {
46         char* result;
47
48         result = strrchr(pathName, '/');
49         if (result) {
50                 return result + 1;
51         }
52         return pathName;
53 }
54
55 int scaleProgress(char* fileName, double progress) {
56         int columns;
57
58         columns = getColumns() - 3 - strlen(fileName);
59         if (columns < 2) {
60                 columns = 2;
61         }
62
63         return (int) (columns * progress);
64 }
65
66 void printProgress(char* fileName, int currentProgress) {
67         char* shortFileName;
68         int totalColumns;
69         int columns;
70         int i;
71
72         shortFileName = fileName;
73         totalColumns = getColumns();
74         columns = totalColumns - 3 - strlen(fileName);
75
76         if (columns < 2) {
77                 shortFileName = fileName + (strlen(fileName) - (totalColumns - 8));
78                 columns = 2;
79         }
80
81         if (shortFileName != fileName) {
82                 printf("...%s", shortFileName);
83         } else {
84                 printf("%s", fileName);
85         }
86         printf(" [");
87         for (i = 0; i < columns; i++) {
88                 printf((i < currentProgress) ? "*" : ".");
89         }
90         printf("]\r");
91         fflush(stdout);
92 }
93
94 int copyFileToFile(char* sourceFile, char* destFile) {
95         FILE* inputFile;
96         FILE* outputFile;
97         char* fileName;
98         long long fileSize;
99         size_t read;
100         size_t toWrite;
101         long long writtenTotal = 0;
102         size_t written;
103         int readSize;
104         int bufferSize;
105         void* buffer;
106         double progress;
107         int currentProgress = 0;
108         int lastProgress = -1;
109         struct timeb startTime, endTime;
110         long startTimeMillis, endTimeMillis, spent;
111
112         inputFile = fopen(sourceFile, "rb");
113         if (!inputFile) {
114                 fprintf(stderr, "can not open \"%s\" for reading!\n", sourceFile);
115                 return false;
116         }
117
118         outputFile = fopen(destFile, "wb");
119         if (!outputFile) {
120                 fprintf(stderr, "can not open \"%s\" for writing!\n", destFile);
121                 fclose(inputFile);
122                 return false;
123         }
124
125         fseek(inputFile, 0, SEEK_END);
126         fileSize = ftell(inputFile);
127         fseek(inputFile, 0, SEEK_SET);
128
129         fileName = extractFilename(sourceFile);
130
131         bufferSize = 1 << 20;
132         readSize = bufferSize;
133         buffer = (void*) malloc(bufferSize);
134
135         while (!feof(inputFile)) {
136                 ftime(&startTime);
137                 read = fread(buffer, 1, readSize, inputFile);
138                 if (read) {
139                         toWrite = read;
140                         do {
141                                 written = fwrite(buffer + (read - toWrite), 1, toWrite, outputFile);
142                                 if (!written) {
143                                         if (ferror(outputFile)) {
144                                                 fprintf(stderr, "\nerror writing to \"%s\"!\n", destFile);
145                                                 fclose(inputFile);
146                                                 fclose(outputFile);
147                                                 return false;
148                                         }
149                                 }
150                                 toWrite -= written;
151                         } while (toWrite);
152                         ftime(&endTime);
153                         writtenTotal += written;
154                         progress = (double) writtenTotal / fileSize;
155                         currentProgress = scaleProgress(fileName, progress);
156                         if ((currentProgress != lastProgress) || (redrawProgressBar)) {
157                                 printProgress(fileName, currentProgress);
158                                 lastProgress = currentProgress;
159                                 redrawProgressBar = false;
160                         }
161                         endTimeMillis = endTime.time * 1000 + endTime.millitm;
162                         startTimeMillis = startTime.time * 1000 + startTime.millitm;
163                         spent = endTimeMillis - startTimeMillis;
164                         if (spent >= 200) {
165                                 if (readSize > 2047) {
166                                         readSize >>= 1;
167                                 }
168                         } else if (spent <= 50) {
169                                 if (readSize < (1 << 19)) {
170                                         readSize <<= 1;
171                                 }
172                         }
173                 } else {
174                         if (ferror(inputFile)) {
175                                 fprintf(stderr, "\nerror reading from \"%s\"!\n", sourceFile);
176                                 fclose(inputFile);
177                                 fclose(outputFile);
178                                 return false;
179                         }
180                 }
181         }
182         printf("\n");
183         fflush(stdout);
184         fclose(inputFile);
185         fclose(outputFile);
186
187         return true;
188 }
189
190 int copyFileToDirectory(char *sourceFile, char* destDirectory) {
191         char* fileName;
192         char* destFile;
193         int copyResult;
194
195         fileName = extractFilename(sourceFile);
196         destFile = (char*) calloc(strlen(destDirectory) + 1 + strlen(fileName) + 1, 1);
197         strcat(destFile, destDirectory);
198         strcat(destFile, "/");
199         strcat(destFile, fileName);
200
201         copyResult = copyFileToFile(sourceFile, destFile);
202
203         free(destFile);
204         return copyResult;
205 }
206
207 void printHelp() {
208         printf("Syntax: ccp SRC [SRC [...]] DEST\n\n");
209         printf("SRC must be a file. If DEST is a directory, an arbitrary amount of SRC\n");
210         printf("may be given. If DEST is an existing file or does not exist at all,\n");
211         printf("only one SRC is allowed.\n");
212 }
213
214 int isWritableFile(char *pathName) {
215         struct stat fileStat;
216         int statError;
217         FILE* testFd;
218
219         statError = stat(pathName, &fileStat);
220         if (statError == -1) {
221                 if (errno == ENOENT) {
222                         testFd = fopen(pathName, "w");
223                         if (testFd) {
224                                 fclose(testFd);
225                                 return true;
226                         }
227                 }
228                 return false;
229         }
230         if (S_ISREG(fileStat.st_mode)) {
231                 testFd = fopen(pathName, "ab+");
232                 if (testFd) {
233                         fclose(testFd);
234                         return true;
235                 }
236         }
237         return false;
238 }
239
240 int isDirectory(char *pathName) {
241         struct stat fileStat;
242         int statError;
243
244         statError = stat(pathName, &fileStat);
245         if (statError == -1) {
246                 return false;
247         }
248         return S_ISDIR(fileStat.st_mode);
249 }
250
251 int checkForDirectoryOrNonExistingFile(char* pathName) {
252         return isDirectory(pathName) || isWritableFile(pathName);
253 }
254
255 int main(int argc, char** argv) {
256         char* lastArgument;
257         char* binaryName;
258         int destinationIsDirectory = false;
259         int fileIndex;
260         int moveFiles = false;
261
262         if (argc < 3) {
263                 printHelp();
264                 return 1;
265         }
266         lastArgument = argv[argc - 1];
267         if (!isDirectory(lastArgument)) {
268                 if (isWritableFile(lastArgument)) {
269                         if (argc != 3) {
270                                 printHelp();
271                                 return 1;
272                         }
273                 } else {
274                         fprintf(stderr, "\"%s\" is not a writable file.\n", lastArgument);
275                         return 2;
276                 }
277         } else {
278                 destinationIsDirectory = true;
279         }
280
281         binaryName = extractFilename(argv[0]);
282         if (!strcmp(binaryName, "cmv")) {
283                 moveFiles = true;
284         }
285
286         signal(SIGWINCH, handleWindowResize);
287         for (fileIndex = 1; fileIndex < (argc - 1); fileIndex++) {
288                 if (destinationIsDirectory) {
289                         if (copyFileToDirectory(argv[fileIndex], lastArgument) && moveFiles) {
290                                 unlink(argv[fileIndex]);
291                         }
292                 } else {
293                         if (copyFileToFile(argv[fileIndex], lastArgument) && moveFiles) {
294                                 unlink(argv[fileIndex]);
295                         }
296                 }
297         }
298
299         return 0;
300 }