1/* This file was written by Jim Kingdon, and is hereby placed
2   in the public domain.  */
3
4#include <Wtypes.h>
5#include <stdio.h>
6#include <direct.h> /* For chdir */
7
8#include "pubscc.h"
9
10/* We get to put whatever we want here, and the caller will pass it
11   to us, so we don't need any global variables.  This is the
12   "void *context_arg" argument to most of the Scc* functions.  */
13struct context {
14    FILE *debuglog;
15    /* Value of the CVSROOT we are currently working with (that is, the
16       "open project" in SCC terminology), malloc'd, or NULL if there is
17       no project currently open.  */
18    char *root;
19    /* Local directory (working directory in CVS parlance).  */
20    char *local;
21    SCC_outproc outproc;
22};
23
24/* In addition to context_arg, most of the Scc* functions take a
25   "HWND window" argument.  This is so that we can put up dialogs.
26   The window which is passed in is the IDE's window, which we
27   should use as the parent of dialogs that we put up.  */
28
29#include <windows.h>
30
31/* Report a malloc error and return the SCC_return_* value which the
32   caller should return to the IDE.  Probably this should be getting
33   the window argument too, but for the moment we don't need it.
34   Note that we only use this for errors which occur after the
35   context->outproc is set up.  */
36SCC_return
37malloc_error (struct context *context)
38{
39    (*context->outproc) ("Out of memory\n", SCC_outproc_error);
40    return SCC_return_non_specific_error;
41}
42
43/* Return the version of the SCC spec, major version in the high word,
44   minor version in the low word.  */
45LONG
46SccGetVersion (void)
47{
48    /* We implement version 1.1 of the spec.  */
49    return 0x10001;
50}
51
52SCC_return
53SccInitialize (void **contextp, HWND window, LPSTR caller, LPSTR name,
54               LPLONG caps, LPSTR path, LPDWORD co_comment_len,
55               LPDWORD comment_len)
56{
57    struct context *context;
58    FILE *fp;
59    fp = fopen ("d:\\debug.scc", "w");
60    if (fp == NULL)
61        /* Do what?  Return some error value?  */
62        abort ();
63    context = malloc (sizeof (struct context));
64    if (context == NULL)
65    {
66        fprintf (fp, "Out of memory\n");
67        fclose (fp);
68        /* Do what?  Return some error?  */
69        abort ();
70    }
71    context->debuglog = fp;
72    context->root = NULL;
73    *contextp = context;
74    fprintf (fp, "Made it into SccInitialize!\n");
75    *caps = (SCC_cap_GetProjPath
76	     | SCC_cap_AddFromScc
77	     | SCC_cap_want_outproc);
78
79    /* I think maybe this should have some more CVS-like
80       name, like "CVS Root", if we decide that is what
81       a SCC "project" is.  */
82    strncpy (path, "CVS Project:", SCC_max_init_path);
83    fprintf (fp, "Caller name is %s\n", caller);
84    strncpy (name, "CVS", SCC_max_name);
85    /* CVS has no limit on comment length.  But I suppose
86       we need to return a value which is small enough for
87       a caller to allocate a buffer this big.  Not that I
88       would write a caller that way, but.....  */
89    *co_comment_len = 8192;
90    *comment_len = 8192;
91    fflush (fp);
92    return SCC_return_success;
93}
94
95SCC_return
96SccUninitialize (void *context_arg)
97{
98    struct context *context = (struct context *)context_arg;
99    if (ferror (context->debuglog))
100	/* FIXME: return error value...  */
101    if (fclose (context->debuglog) == EOF)
102        /* FIXME: return error value, I think.  */
103        ;
104    free (context);
105    return SCC_return_success;
106}
107
108SCC_return
109SccOpenProject (void *context_arg, HWND window, LPSTR user,
110		LPSTR project, LPSTR local_proj, LPSTR aux_proj,
111		LPSTR comment, SCC_outproc outproc,
112		LONG flags)
113{
114    struct context *context = (struct context *)context_arg;
115
116    /* This can happen if the IDE opens a project which is not under
117       CVS control.  I'm not sure whether checking for aux_proj
118       being "" is the right way to detect this case, but it seems
119       it should work because I think that the source code control
120       system is what has control over the contents of aux_proj.  */
121    if (aux_proj[0] == '\0')
122	return SCC_return_unknown_project;
123
124    context->root = malloc (strlen (aux_proj) + 5);
125    if (context->root == NULL)
126	return SCC_return_non_specific_error;
127    strcpy (context->root, aux_proj);
128    /* Since we don't yet support creating projects, we don't
129       do anything with flags.  */
130
131    if (outproc == 0)
132    {
133	/* This supposedly can happen if the IDE chooses not to implement
134	   the outproc feature.  */
135	fprintf (context->debuglog, "Uh oh.  outproc is a null pointer\n");
136	context->root = NULL;
137	fflush (context->debuglog);
138	return SCC_return_non_specific_error;
139    }
140    context->outproc = outproc;
141
142    fprintf (context->debuglog, "SccOpenProject (aux_proj=%s)\n", aux_proj);
143
144    context->local = malloc (strlen (local_proj) + 5);
145    if (context->local == NULL)
146	return malloc_error (context);
147    strcpy (context->local, local_proj);
148
149    fflush (context->debuglog);
150    return SCC_return_success;
151}
152
153SCC_return
154SccCloseProject (void *context_arg)
155{
156    struct context *context = (struct context *)context_arg;
157    fprintf (context->debuglog, "SccCloseProject\n");
158    fflush (context->debuglog);
159    if (context->root != NULL)
160	free (context->root);
161    context->root = NULL;
162    return SCC_return_success;
163}
164
165/* cvs get.  */
166SCC_return
167SccGet (void *context_arg, HWND window, LONG num_files,
168        LPSTR *file_names,
169	LONG options,
170	void *prov_options)
171{
172    struct context *context = (struct context *)context_arg;
173    int i;
174    char *fname;
175
176    fprintf (context->debuglog, "SccGet: %d; files:", num_files);
177#if 1
178    for (i = 0; i < num_files; ++i)
179    {
180	fprintf (context->debuglog, "%s ", file_names[i]);
181    }
182#endif
183    fprintf (context->debuglog, "\n");
184    if (options & SCC_cmdopt_dir)
185	fprintf (context->debuglog, "  Get all\n");
186    /* Should be using this flag to set -R vs. -l.  */
187    if (options & SCC_cmdopt_recurse)
188	fprintf (context->debuglog, "  recurse\n");
189
190    for (i = 0; i < num_files; ++i)
191    {
192	/* As with all file names passed to us by the SCC, these
193	   file names are absolute pathnames.  I think they will
194	   tend to be paths within context->local, although I
195	   don't know whether there are any exceptions to that.  */
196	fname = file_names[i];
197	fprintf (context->debuglog, "%s ", fname);
198	/* Here we would write to the file named fname.  */
199    }
200    fprintf (context->debuglog, "\nExiting SccGet\n");
201    fflush (context->debuglog);
202    return SCC_return_success;
203}
204
205/* cvs edit.  */
206SCC_return
207SccCheckout (void *context_arg, HWND window, LONG num_files,
208             LPSTR *file_names, LPSTR comment,
209	     LONG options,
210             void *prov_options)
211{
212    struct context *context = (struct context *)context_arg;
213    fprintf (context->debuglog, "SccCheckout num_files=%ld\n", num_files);
214    fflush (context->debuglog);
215    /* For the moment we say that all files are not ours.  I'm not sure
216       whether this is ever necessary; that is, whether the IDE will call
217       us except where we have told the IDE that a file is under source
218       control.  */
219    /* I'm not sure what we would do if num_files > 1 and we wanted to
220       return different statuses for different files.  */
221    return SCC_return_non_scc_file;
222}
223
224/* cvs ci.  */
225SCC_return
226SccCheckin (void *context_arg, HWND window, LONG num_files,
227            LPSTR *file_names, LPSTR comment,
228	    LONG options,
229            void *prov_options)
230{
231    return SCC_return_not_supported;
232}
233
234/* cvs unedit.  */
235SCC_return
236SccUncheckout (void *context_arg, HWND window, LONG num_files,
237               LPSTR *file_names,
238	       LONG options,
239	       void *prov_options)
240{
241    return SCC_return_not_supported;
242}
243
244/* cvs add + cvs ci, more or less, I think (but see also
245   the "keep checked out" flag in options).  */
246SCC_return
247SccAdd (void *context_arg, HWND window, LONG num_files,
248        LPSTR *file_names, LPSTR comment,
249	LONG *options,
250        void *prov_options)
251{
252    return SCC_return_not_supported;
253}
254
255/* cvs rm -f + cvs ci, I think.  Should barf if SCC_REMOVE_KEEP
256   (or maybe just put the file there, as if the user had removed
257   it and then done a "copy <saved-file> <filename>".  */
258SCC_return
259SccRemove (void *context_arg, HWND window, LONG num_files,
260           LPSTR *file_names, LPSTR comment,
261	   LONG options,
262           void *prov_options)
263{
264    return SCC_return_not_supported;
265}
266
267/* mv, cvs add, cvs rm, and cvs ci, I think.  */
268SCC_return
269SccRename (void *context_arg, HWND window, LPSTR old_name,
270           LPSTR new_name)
271{
272    return SCC_return_not_supported;
273}
274
275/* If SCC_cmdopt_compare_files, SCC_cmdopt_consult_checksum, or
276   SCC_cmdopt_consult_timestamp, then we are supposed to silently
277   return a status, without providing any information directly to the
278   user.  For no args or checksum (which we fall back to full compare)
279   basically a call to No_Diff or ? in the client case.  For
280   timestamp, just a Classify_File.  Now, if contents not set, then
281   want to do a cvs diff, and preferably start up WinDiff or something
282   (to be determined, for now perhaps could just return text via
283   outproc).  */
284SCC_return
285SccDiff (void *context_arg, HWND window, LPSTR file_name,
286         LONG options,
287	 void *prov_options)
288{
289    return SCC_return_not_supported;
290}
291
292/* cvs log, I presume.  If we want to get fancier we could bring
293   up a screen more analogous to the tkCVS log window, let the user
294   do "cvs update -r", etc.  */
295SCC_return
296SccHistory (void *context_arg, HWND window, LONG num_files,
297            LPSTR *file_names,
298	    LONG options,
299	    void *prov_options)
300{
301    return SCC_return_not_supported;
302}
303
304/* cvs status, presumably.  */
305SCC_return
306SccProperties (void *context_arg, HWND window, LPSTR file_name)
307{
308    return SCC_return_not_supported;
309}
310
311/* Not sure what this should do.  The most obvious thing is some
312   kind of front-end to "cvs admin" but I'm not actually sure that
313   is the most useful thing.  */
314SCC_return
315SccRunScc (void *context_arg, HWND window, LONG num_files,
316           LPSTR *file_names)
317{
318    return SCC_return_not_supported;
319}
320
321/* Lots of things that we could do here.  Options to get/update
322   such as -r -D -k etc. just for starters.  Note that the terminology is
323   a little confusing here.  This function relates to "provider options"
324   (prov_options) which are a way for us to provide extra dialogs beyond
325   the basic ones for a particular command.  It is unrelated to "command
326   options" (SCC_cmdopt_*).  */
327SCC_return
328SccGetCommandOptions (void *context_arg, HWND window,
329                      enum SCC_command command,
330                      void **prov_optionsp)
331{
332    return SCC_return_not_supported;
333}
334
335/* Not existing CVS functionality, I don't think.
336   Need to be able to tell user about what files
337   are out there without actually getting them.  */
338SCC_return
339SccPopulateList (void *context_arg, enum SCC_command command,
340                 LONG num_files,
341                 LPSTR *file_names, SCC_popul_proc populate,
342                 void *callerdat,
343		 LONG options)
344{
345    return SCC_return_success;
346}
347
348/* cvs status, sort of.  */
349SCC_return
350SccQueryInfo (void *context_arg, LONG num_files, LPSTR *file_names,
351              LPLONG status)
352{
353    return SCC_return_not_supported;
354}
355
356/* Like QueryInfo, but fast and for only a single file.  For example, the
357   development environment might call this quite frequently to keep its
358   screen display updated.  */
359SCC_return
360SccGetEvents (void *context_arg, LPSTR file_name,
361	      LPLONG status,
362              LPLONG events_remaining)
363{
364    /* They say this is supposed to only return cached status
365       information, not go to disk or anything.  I assume that
366       QueryInfo and probably the usual calls like Get would cause
367       us to cache the status in the first place.  */
368    return SCC_return_success;
369}
370
371/* This is where the user gives us the CVSROOT.  */
372SCC_return
373SccGetProjPath (void *context_arg, HWND window, LPSTR user,
374                LPSTR proj_name, LPSTR local_proj, LPSTR aux_proj,
375                BOOL allow_change, BOOL *new)
376{
377    /* For now we just hardcode the CVSROOT.  In the future we will
378       of course prompt the user for it (simple implementation would
379       have them supply a string; potentially better implementation
380       would have menus or something for access methods and so on,
381       although it might also have a way of bypassing that in case
382       CVS supports new features that the GUI code doesn't
383       understand).  We probably will also at some point want a
384       "project" to encompass both a CVSROOT and a directory or
385       module name within that CVSROOT, but we don't try to handle
386       that yet either.  We also will want to be able to use "user"
387       instead of having the username encoded in the aux_proj or
388       proj_name, probably.  */
389
390    struct context *context = (struct context *)context_arg;
391    fprintf (context->debuglog, "SccGetProjPath called\n");
392
393    /* At least for now we leave the proj_name alone, and just use
394       the aux_proj.  */
395    strncpy (proj_name, "zwork", SCC_max_path);
396    strncpy (aux_proj, ":server:harvey:/home/kingdon/zwork/cvsroot",
397	     SCC_max_path);
398    if (local_proj[0] == '\0' && allow_change)
399	strncpy (local_proj, "d:\\sccwork", SCC_max_path);
400    /* I don't think I saw anything in the spec about this,
401       but let's see if it helps.  */
402    if (_chdir (local_proj) < 0)
403	fprintf (context->debuglog, "Error in chdir: %s", strerror (errno));
404
405    if (*new)
406	/* It is OK for us to prompt the user for creating a new
407	   project.  */
408	/* We will say that the user said to create a new one.  */
409	*new = 1;
410
411    fflush (context->debuglog);
412    return SCC_return_success;
413}
414
415/* Pretty much similar to SccPopulateList.  */
416SCC_return
417SccAddFromScc (void *context_arg, HWND window, LONG *files,
418               char ***file_names)
419{
420    struct context *context = (struct context *)context_arg;
421
422    /* For now we have hardcoded the notion that there are two files,
423       foo.c and bar.c.  */
424#define NUM_FILES 2
425    if (files == NULL)
426    {
427	char **p;
428
429	/* This means to free the memory that is allocated for
430	   file_names.  */
431	for (p = *file_names; *p != NULL; ++p)
432	{
433	    fprintf (context->debuglog, "Freeing %s\n", *p);
434	    free (*p);
435	}
436    }
437    else
438    {
439	*file_names = malloc ((NUM_FILES + 1) * sizeof (char **));
440	if (*file_names == NULL)
441	    return malloc_error (context);
442	(*file_names)[0] = malloc (80);
443	if ((*file_names)[0] == NULL)
444	    return malloc_error (context);
445	strcpy ((*file_names)[0], "foo.c");
446	(*file_names)[1] = malloc (80);
447	if ((*file_names)[1] == NULL)
448	    return malloc_error (context);
449	strcpy ((*file_names)[1], "bar.c");
450	(*file_names)[2] = NULL;
451	*files = 2;
452
453	/* Are we supposed to also Get the files?  Or is the IDE
454	   next going to call SccGet on each one?  The spec doesn't
455	   say explicitly.  */
456    }
457    fprintf (context->debuglog, "Success in SccAddFromScc\n");
458    fflush (context->debuglog);
459    return SCC_return_success;
460}
461
462/* This changes several aspects of how we interact with the IDE.  */
463SCC_return
464SccSetOption (void *context_arg,
465	      LONG option,
466	      LONG val)
467{
468    return SCC_return_success;
469}
470