Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Full text session search
  • Loading branch information
davechri committed Oct 24, 2023
commit 5faf3a80e8c134d959d9a204a7689bb60d2cfa10
94 changes: 87 additions & 7 deletions client/src/components/SessionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,32 @@ import { observer } from 'mobx-react-lite';
import CloseIcon from "@material-ui/icons/Close";
import SessionStore from '../store/SessionStore';
import { snapshotStore } from '../store/SnapshotStore';
import React from 'react';
import React, { useEffect } from 'react';
import DeleteDialog from './DeleteDialog';
import { apFileSystem } from '../store/APFileSystem';

type Props = {
open: boolean,
onClose: () => void,
store: SessionStore,
};
const SessionModal = observer(({ open, onClose, store }: Props) => {
const [filterValue, setFilterValue] = React.useState('');
const [filterValues, setFilterValues] = React.useState<string[]>([]);
const [titleValue, setTitleValue] = React.useState('');
const [searchValue, setSearchValue] = React.useState('');
const [openDeleteDialog, setOpenDeleteDialog] = React.useState(false);
const [pendingDeleteIndex, setPendingDeleteIndex] = React.useState(-1);
const [searchType, setSearchType] = React.useState<string>('Title');

useEffect(() => {
setTitleValue('');
setSearchValue('');
setSearchType('Title');
filterValues.splice(0, filterValues.length);
}, [open]);

function close() {
snapshotStore.setUpdating(false);
onClose();
}

Expand All @@ -32,6 +44,16 @@ const SessionModal = observer(({ open, onClose, store }: Props) => {
snapshotStore.setUpdating(false);
}

function isFilterValueMatch(sessionName: string) {
if (filterValues.length === 0) return true;
for (const value of filterValues) {
if (sessionName.indexOf(value) !== -1) {
return true;
}
}
return false;
}

return (
<>
<Modal
Expand All @@ -46,18 +68,76 @@ const SessionModal = observer(({ open, onClose, store }: Props) => {
<h3>Sessions</h3>
<div style={{ borderTop: 'solid steelblue', paddingTop: '.5rem' }}>
<div className="no-capture-modal__scroll-container">
<input type="search" className="form-control" style={{ marginTop: '1rem' }}
placeholder="Filter..."
onChange={(e) => setFilterValue(e.target.value)}
value={filterValue} />
<div style={{ display: 'flex', marginTop: '1rem' }}>

<select className="form-control btn btn-primary"
disabled={snapshotStore.isUpdating()}
style={{ width: '7rem' }}
onChange={e => {
setSearchType(e.target.value);
if (e.target.value === 'Title') {
setFilterValues([titleValue]);
}
}}
value={searchType}
>
<option selected={searchType === 'Title'}>Title</option>
<option selected={searchType === 'Full Text'}>Full Text</option>
</select>

{searchType === 'Title' ?
<input type="search" className="form-control"
onChange={(e) => {
setTitleValue(e.target.value);
setFilterValues([e.target.value]);
}}
value={titleValue} />
:
<input type="search" className="form-control"
placeholder="Hit enter to search"
disabled={snapshotStore.isUpdating()}
onChange={(e) => setSearchValue(e.target.value)}
onKeyUp={async (e) => {
if (e.keyCode === 13) {
if (searchValue === '') {
setFilterValues([]);
} else {
snapshotStore.setUpdating(true);
//console.log('enter');
apFileSystem.grepDir('sessions', searchValue)
.then((files) => {
//console.log(files);
if (Array.isArray(files)) {
const values: string[] = [];
for (const file of files) {
const value = file.split('/')[1];
values.push(value);
}
setFilterValues(values);
} else {
console.error(files);
}
snapshotStore.setUpdating(false);
})
.catch((e) => {
snapshotStore.setUpdating(false);
console.error(e);
});
}
}
}}
value={searchValue} />
}

</div>
<List>
{store.getSessionList().length === 0 &&
<div className="center"
style={{ marginTop: 'calc( 50vh - 72px' }}>
No saved sessions found
</div>}
{store.getSessionList().map((sessionName, i) => (
sessionName.indexOf(filterValue) !== -1 &&
(isFilterValueMatch(sessionName)) &&
<ListItem key={i}
style={{
display: 'flex', alignItems: 'center',
Expand Down
8 changes: 8 additions & 0 deletions client/src/store/APFileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ export default class APFileSystem {
});
}

public async grepDir(path: string, match: string): Promise<string[]> {
return new Promise<string[]>((resolve) => {
this.socket?.emit('grepDir', path, match, (files: string[]) => {
resolve(files);
});
});
}

// readFile
public async readFile(path: string): Promise<string> {
const chunks: string[] = [];
Expand Down
42 changes: 42 additions & 0 deletions server/src/APFileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import fs from 'fs';
import { Socket } from 'socket.io';
import ConsoleLog from './ConsoleLog';
import Paths from './Paths';
import { commandExists } from './interceptors/util/fs';
const spawn = require('child_process').spawn;
const rmdir = require('rimraf');

export default class APFileSystem {
Expand Down Expand Up @@ -94,6 +96,27 @@ export default class APFileSystem {
}
})

// grepDir - find all files in a directory with a matching string
this.socket.on('grepDir', async (path: string, match: string, callback: (files: string[]) => void) => {
try {
const subDir = Paths.platform(path);

let output: string;
if (await commandExists('parallel')) {
output = await run(`find ${subDir} -type f -print0 | parallel -0 grep -l -m 1 ${match} {}`, Paths.getDataDir());
} else {
output = await run(`find ${subDir} -type f -exec grep -l -m 1 ${match} {} +`, Paths.getDataDir());
}

const files = output.toString().split('\n')

ConsoleLog.debug('ApFileSystem.grepDir', subDir, match, files);
callback(files);
} catch (e) {
console.log(e);
}
})

// readFile
this.socket.on('readFile', (path: string, offset: number, chunkSize: number, callback: (data: string, eof: boolean) => void) => {
try {
Expand All @@ -112,4 +135,23 @@ export default class APFileSystem {
}
})
}
}

async function run(command: string, cwd: string): Promise<string> {
console.log(command);
let response = ''
await new Promise(resolve => {
const tokens = command.split(' ');
const p = spawn(tokens[0], tokens.slice(1), { cwd, shell: true })
p.stdout.on('data', (data: Buffer) => {
//console.log(data.toString());
response += data.toString();
})
p.stderr.on('data', (data: Buffer) => {
console.error(data.toString());
})
p.on('exit', resolve)
})
console.log(response)
return response;
}