/ Check-in [840401cc]
Login
Overview
Comment:Improve parsing of ".ar" commands. Add new test file for the same.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | sqlar-shell-support
Files: files | file ages | folders
SHA3-256:840401cc8ce3a09e0663b46973ecd2856d9607be71d2d1e9b21f7df7a82dcbe5
User & Date: dan 2017-12-09 17:58:02
Context
2017-12-09
18:28
Add support for -C to ".ar x". check-in: 8cd70960 user: dan tags: sqlar-shell-support
17:58
Improve parsing of ".ar" commands. Add new test file for the same. check-in: 840401cc user: dan tags: sqlar-shell-support
2017-12-07
21:03
Add the ".ar x" command to the shell. For extracting the contents of sqlar archives. check-in: 0cc699d1 user: dan tags: sqlar-shell-support
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/shell.c.in.

4111
4112
4113
4114
4115
4116
4117
4118
4119


















































































































4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
....
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
....
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
....
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
....
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
4288
4289
4290
4291
4292
4293
4294
4295
4296
4297
4298
4299
4300
4301
4302
4303
4304
4305
4306
4307
4308
4309
4310
4311
4312
4313
4314
4315
4316
4317
4318
4319
4320
4321
static void shellReset(
  int *pRc, 
  sqlite3_stmt *pStmt
){
  int rc = sqlite3_reset(pStmt);
  if( *pRc==SQLITE_OK ) *pRc = rc;
}

/*


















































































































** Implementation of .ar "eXtract" command. 
*/
static int arExtractCommand(ShellState *p, int bVerbose){
  const char *zSql1 = 
    "SELECT name, writefile(name, "
    "CASE WHEN (data AND sz>=0 AND sz!=length(data)) THEN uncompress(data) "
    "   ELSE data END, "
    "mode) FROM sqlar";
  const char *zSql2 = "SELECT name, mtime FROM sqlar"; 

................................................................................
  int rc = SQLITE_OK;

  memset(times, 0, sizeof(times));
  times[0].tv_sec = time(0);

  shellPrepare(p, &rc, zSql1, &pSql);
  while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
    if( bVerbose ){
      raw_printf(stdout, "%s\n", sqlite3_column_text(pSql, 0));
    }
  }
  shellFinalize(&rc, pSql);

  shellPrepare(p, &rc, zSql2, &pSql);
  while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
................................................................................
** Create the "sqlar" table in the database if it does not already exist.
** Then add each file in the azFile[] array to the archive. Directories
** are added recursively. If argument bVerbose is non-zero, a message is
** printed on stdout for each file archived.
*/
static int arCreateCommand(
  ShellState *p,                  /* Shell state pointer */
  char **azFile,                  /* Array of files to add to archive */
  int nFile,                      /* Number of entries in azFile[] */
  int bVerbose                    /* True to be verbose on stdout */
){
  const char *zSql = 
    "WITH f(n, m, t, d) AS ("
    "  SELECT name, mode, mtime, data FROM fsentry(:1) UNION ALL "
    "  SELECT n || '/' || name, mode, mtime, data "
    "      FROM f, fsdir(n) WHERE (m&?)"
    ") SELECT * FROM f";
................................................................................
  rc = sqlite3_exec(p->db, "SAVEPOINT ar;", 0, 0, 0);
  if( rc!=SQLITE_OK ) return rc;

  rc = sqlite3_exec(p->db, zSqlar, 0, 0, 0);
  shellPrepare(p, &rc, zInsert, &pInsert);
  shellPrepare(p, &rc, zSql, &pStmt);

  for(i=0; i<nFile && rc==SQLITE_OK; i++){
    sqlite3_bind_text(pStmt, 1, azFile[i], -1, SQLITE_STATIC);
    sqlite3_bind_int(pStmt, 2, S_IFDIR);
    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
      int sz;
      const char *zName = (const char*)sqlite3_column_text(pStmt, 0);
      int mode = sqlite3_column_int(pStmt, 1);
      unsigned int mtime = sqlite3_column_int(pStmt, 2);

      if( bVerbose ){
        raw_printf(stdout, "%s\n", zName);
      }

      sqlite3_bind_text(pInsert, 1, zName, -1, SQLITE_STATIC);
      sqlite3_bind_int(pInsert, 2, mode);
      sqlite3_bind_int64(pInsert, 3, (sqlite3_int64)mtime);

................................................................................
** Implementation of ".ar" dot command.
*/
static int arDotCommand(
  ShellState *pState,             /* Current shell tool state */
  char **azArg,                   /* Array of arguments passed to dot command */
  int nArg                        /* Number of entries in azArg[] */
){
  int bVerbose = 0;
  char cmd = 0;
  int i;
  int n1;
  if( nArg<=1 ) goto usage;

  n1 = strlen(azArg[1]);
  for(i=0; i<n1; i++){
    char c = azArg[1][i];
    if( c=='c' || c=='x' ){
      if( cmd ) goto usage;
      cmd = c;
    }
    else if( c=='v' ){
      bVerbose = 1;
    }else{
      goto usage;
    }
  }

  if( cmd=='c' ){
    return arCreateCommand(pState, &azArg[2], nArg-2, bVerbose);
  }

  if( cmd=='x' ){
    if( nArg!=2 ) goto usage;
    return arExtractCommand(pState, bVerbose);
  }

 usage:
  raw_printf(stderr, "Usage %s sub-command ?args...?\n", azArg[0]);
  return SQLITE_ERROR;
}


/*
** If an input line begins with "." then invoke this routine to
** process that line.
**








|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


|







 







|







 







|
<
<







 







|
|







|







 







|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
<
<
<
<
<
<







4111
4112
4113
4114
4115
4116
4117
4118
4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151
4152
4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235
4236
4237
4238
4239
4240
4241
4242
4243
....
4246
4247
4248
4249
4250
4251
4252
4253
4254
4255
4256
4257
4258
4259
4260
....
4277
4278
4279
4280
4281
4282
4283
4284


4285
4286
4287
4288
4289
4290
4291
....
4312
4313
4314
4315
4316
4317
4318
4319
4320
4321
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331
4332
4333
4334
4335
....
4388
4389
4390
4391
4392
4393
4394
4395
4396
4397
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
4417
4418
4419







4420
4421
4422
4423
4424
4425
4426
static void shellReset(
  int *pRc, 
  sqlite3_stmt *pStmt
){
  int rc = sqlite3_reset(pStmt);
  if( *pRc==SQLITE_OK ) *pRc = rc;
}

/* 
** Structure representing a single ".ar" command.
*/
typedef struct ArCommand ArCommand;
struct ArCommand {
  int eCmd;                       /* An AR_CMD_* value */
  const char *zFile;              /* --file argument, or NULL */
  const char *zDir;               /* --directory argument, or NULL */
  int bVerbose;                   /* True if --verbose */
  int nArg;                       /* Number of command arguments */
  char **azArg;                   /* Array of command arguments */
};

/*
** Print a usage message for the .ar command to stderr and return SQLITE_ERROR.
*/
static int arUsage(void){
  /* todo */
  raw_printf(stderr, "error in .ar command line\n");
  return SQLITE_ERROR;
}

/*
** Values for ArCommand.eCmd.
*/
#define AR_CMD_CREATE  1
#define AR_CMD_EXTRACT 2
#define AR_CMD_LIST    3
#define AR_CMD_UPDATE  4

/*
** Parse the command line for an ".ar" command. The results are written into
** structure (*pAr). SQLITE_OK is returned if the command line is parsed
** successfully, otherwise an error message is written to stderr and 
** SQLITE_ERROR returned.
*/
static int arParseCommand(
  char **azArg,                   /* Array of arguments passed to dot command */
  int nArg,                       /* Number of entries in azArg[] */
  ArCommand *pAr                  /* Populate this object */
){
  if( nArg<=1 ){
    return arUsage();
  }else{
    char *z = azArg[1];
    memset(pAr, 0, sizeof(ArCommand));

    if( z[0]!='-' ){
      /* Traditional style [tar] invocation */
      int i;
      int iArg = 2;
      for(i=0; z[i]; i++){
        switch( z[i] ){
          case 'c':
            if( pAr->eCmd ) return arUsage();
            pAr->eCmd = AR_CMD_CREATE;
            break;
          case 'x':
            if( pAr->eCmd ) return arUsage();
            pAr->eCmd = AR_CMD_EXTRACT;
            break;
          case 't':
            if( pAr->eCmd ) return arUsage();
            pAr->eCmd = AR_CMD_LIST;
            break;
          case 'u':
            if( pAr->eCmd ) return arUsage();
            pAr->eCmd = AR_CMD_UPDATE;
            break;

          case 'v':
            pAr->bVerbose = 1;
            break;
          case 'f':
            if( iArg>=nArg ) return arUsage();
            pAr->zFile = azArg[iArg++];
            break;
          case 'C':
            if( iArg>=nArg ) return arUsage();
            pAr->zDir = azArg[iArg++];
            break;

          default:
            return arUsage();
        }
      }

      pAr->nArg = nArg-iArg;
      if( pAr->nArg>0 ){
        pAr->azArg = &azArg[iArg];
      }
    }
  }

  return SQLITE_OK;
}

/*
** Implementation of .ar "Update" command. 
*/
static int arUpdateCmd(ShellState *p, ArCommand *pAr){
  raw_printf(stderr, "todo...\n");
  return SQLITE_OK;
}

/*
** Implementation of .ar "lisT" command. 
*/
static int arListCommand(ShellState *p, ArCommand *pAr){
  raw_printf(stderr, "todo...\n");
  return SQLITE_OK;
}


/*
** Implementation of .ar "eXtract" command. 
*/
static int arExtractCommand(ShellState *p, ArCommand *pAr){
  const char *zSql1 = 
    "SELECT name, writefile(name, "
    "CASE WHEN (data AND sz>=0 AND sz!=length(data)) THEN uncompress(data) "
    "   ELSE data END, "
    "mode) FROM sqlar";
  const char *zSql2 = "SELECT name, mtime FROM sqlar"; 

................................................................................
  int rc = SQLITE_OK;

  memset(times, 0, sizeof(times));
  times[0].tv_sec = time(0);

  shellPrepare(p, &rc, zSql1, &pSql);
  while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
    if( pAr->bVerbose ){
      raw_printf(stdout, "%s\n", sqlite3_column_text(pSql, 0));
    }
  }
  shellFinalize(&rc, pSql);

  shellPrepare(p, &rc, zSql2, &pSql);
  while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
................................................................................
** Create the "sqlar" table in the database if it does not already exist.
** Then add each file in the azFile[] array to the archive. Directories
** are added recursively. If argument bVerbose is non-zero, a message is
** printed on stdout for each file archived.
*/
static int arCreateCommand(
  ShellState *p,                  /* Shell state pointer */
  ArCommand *pAr                  /* Command arguments and options */


){
  const char *zSql = 
    "WITH f(n, m, t, d) AS ("
    "  SELECT name, mode, mtime, data FROM fsentry(:1) UNION ALL "
    "  SELECT n || '/' || name, mode, mtime, data "
    "      FROM f, fsdir(n) WHERE (m&?)"
    ") SELECT * FROM f";
................................................................................
  rc = sqlite3_exec(p->db, "SAVEPOINT ar;", 0, 0, 0);
  if( rc!=SQLITE_OK ) return rc;

  rc = sqlite3_exec(p->db, zSqlar, 0, 0, 0);
  shellPrepare(p, &rc, zInsert, &pInsert);
  shellPrepare(p, &rc, zSql, &pStmt);

  for(i=0; i<pAr->nArg && rc==SQLITE_OK; i++){
    sqlite3_bind_text(pStmt, 1, pAr->azArg[i], -1, SQLITE_STATIC);
    sqlite3_bind_int(pStmt, 2, S_IFDIR);
    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
      int sz;
      const char *zName = (const char*)sqlite3_column_text(pStmt, 0);
      int mode = sqlite3_column_int(pStmt, 1);
      unsigned int mtime = sqlite3_column_int(pStmt, 2);

      if( pAr->bVerbose ){
        raw_printf(stdout, "%s\n", zName);
      }

      sqlite3_bind_text(pInsert, 1, zName, -1, SQLITE_STATIC);
      sqlite3_bind_int(pInsert, 2, mode);
      sqlite3_bind_int64(pInsert, 3, (sqlite3_int64)mtime);

................................................................................
** Implementation of ".ar" dot command.
*/
static int arDotCommand(
  ShellState *pState,             /* Current shell tool state */
  char **azArg,                   /* Array of arguments passed to dot command */
  int nArg                        /* Number of entries in azArg[] */
){
  ArCommand cmd;
  int rc;
  rc = arParseCommand(azArg, nArg, &cmd);
  if( rc==SQLITE_OK ){
    switch( cmd.eCmd ){
      case AR_CMD_CREATE:
        rc = arCreateCommand(pState, &cmd);
        break;

      case AR_CMD_EXTRACT:
        rc = arExtractCommand(pState, &cmd);
        break;

      case AR_CMD_LIST:
        rc = arListCommand(pState, &cmd);
        break;

      default:
        assert( cmd.eCmd==AR_CMD_UPDATE );
        rc = arUpdateCmd(pState, &cmd);
        break;
    }
  }

  return rc;







}


/*
** If an input line begins with "." then invoke this routine to
** process that line.
**

Added test/shell8.test.







































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# 2017 December 9
#
# The author disclaims copyright to this source code.  In place of
# a legal notice, here is a blessing:
#
#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#***********************************************************************
#
# Test the shell tool ".ar" command.
#

set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix shell8
set CLI [test_find_cli]

proc populate_dir {dirname spec} {
  # First delete the current tree, if one exists.
  file delete -force $dirname
  
  # Recreate the root of the new tree.
  file mkdir $dirname

  # Add each file to the new tree.
  foreach {f d} $spec {
    set path [file join $dirname $f]
    file mkdir [file dirname $path]
    set fd [open $path w]
    puts -nonewline $fd $d
    close $fd
  }
}

proc dir_to_list {dirname} {
  set res [list]
  foreach f [glob -nocomplain $dirname/*] {
    set mtime [file mtime $f]
    set perm [file attributes $f -perm]
    set relpath [file join {*}[lrange [file split $f] 1 end]]
    lappend res 
    if {[file isdirectory $f]} {
      lappend res [list $relpath / $mtime $perm]
      lappend res {*}[dir_to_list $f]
    } else {
      set fd [open $f]
      set data [read $fd]
      close $fd
      lappend res [list $relpath $data $mtime $perm]
    }
  }
  lsort $res
}

proc dir_compare {d1 d2} {
  set l1 [dir_to_list $d1]
  set l2 [dir_to_list $d1]
  string compare $l1 $l2
}

populate_dir ar1 {
  file1 "abcd" 
  file2 "efgh"
  dir1/file3 "ijkl"
}

set expected [dir_to_list ar1]
# puts "# $expected"
do_test 1.1 {
  forcedelete test_ar.db

  catchcmd test_ar.db ".ar c ar1"
  file delete -force ar1
  catchcmd test_ar.db ".ar x"

  dir_to_list ar1
} $expected



finish_test