Commit 3919a47e authored by Frank Bergmann's avatar Frank Bergmann

- Fixed server-side download

- Added client-side scripts
parent 84468bf2
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>]project-open[ filestorage synchronization</title>
<!--- <script type="text/javascript" src="ext/ext-all.js"></script> -->
<script type="text/javascript" src="ext/ext-all-debug-w-comments.js"></script>
<link rel="stylesheet" href="ext/resources/css/ext-all-gray.css" type="text/css" media="screen">
<script type="text/javascript" src="app.js"></script>
</head>
<body>
<h1>]project-open[ filestorage synchronization</h1>
We are using node <script>document.write(process.versions.node)</script>,
Chrome <script>document.write(process.versions.chrome)</script>,
and Electron <script>document.write(process.versions.electron)</script>.
</body>
</html>
<!-- vi: set ts=8 sts=2 sw=2 et : -->
/*
* ]project-open[ filestorage synchronization
* Copyright (c) 2018 Gonzalo Pérez de Olaguer Córdoba <gonzalo.perez@project-open.com>
* This code is licensed under the GNU GPL version 2.0 or later
*
* This is the electron initialization script.
* It creates a tray icon to control de application.
*/
const {app, Menu, Tray, BrowserWindow, ipcMain} = require('electron')
let tray = null
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win = null
function createWindow () {
/* Persistent state */
const Store = require('electron-store');
const state_store = new Store({ name: 'state' });
w = state_store.get('win.width', 1200)
h = state_store.get('win.height', 600)
// Create the browser window.
win = new BrowserWindow({
title: ']project-open[ filestorage synchronization',
icon: './images/window.png',
show: false,
width: w, height: h
})
// and load the index.html of the app.
win.loadFile('index.html')
// Open the DevTools.
win.webContents.openDevTools()
// Emitted once when the window is ready to be shown.
// fraber 2018-12-18: Don't show by default
// win.once('ready-to-show', () => { win.show() })
// Emitted when the window is closed.
win.on('closed', () => {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
win = null
})
// Emitted when the window is resized.
win.on('resize', () => {
s = win.getSize()
state_store.set({win:{width:s[0],height:s[1]}})
})
}
function showWindow () {
if (win === null) {
createWindow()
} else {
win.show()
}
}
function hideWindow () {
if (win !== null) {
win.hide()
}
}
function closeWindow () {
if (win !== null) {
win.close()
}
}
function toggleWindow () {
if (win === null) {
createWindow()
} else if (win.isVisible()) {
win.hide()
} else {
win.show()
}
}
function sync () {
if (win === null) {
createWindow()
}
// Send an IPC event to the Browser window
win.webContents.send('sync', 'sync');
}
function create_tray_icon () {
tray = new Tray('./images/tray.png')
const contextMenu = Menu.buildFromTemplate([
{label: 'Sync', click: sync},
// {label: 'Show', click: showWindow},
// {label: 'Hide', click: hideWindow},
{label: 'Quit', click: closeWindow}
])
tray.setToolTip(']project-open[ filestorage synchronization')
tray.setContextMenu(contextMenu)
tray.on ('click', toggleWindow)
// Create window and start application, but don't show by default
createWindow();
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', create_tray_icon)
/*
* Data related to a file.
*
* Only the fields required by this application are specified here.
*/
Ext.define('POSync.model.File', {
extend: 'Ext.data.TreeModel',
idProperty: 'id',
fields: [
'id', // The system wide unique ID for this object (-> cr_items.item_id)
// FIXME: could better be file_id
'name', // The name and extension (without path) of the file or folder
'version_id', // The active version of the file (-> cr_revisions.revision_id) (empty string for folders)
'description', // Description of the file or folder
'title', // Title of the file or folder
'iconCls', // Used by ExtJS for the icon, seems to work
// FIXME: should be generated automatically from 'type' (icon-TYPE)
'content_length', // File length in bytes (empty string for folders)
'sha1', // sha-1 Hash of the content (empty string for folders)
'expanded', // true or false (without quotes), default state for tree
'type', // 'folder' for folders and the mime-type for file
'leaf', // false for folders, true for files
// added by SALO
'level', // FIXME: let ExtJS calculate this
'mime_type', // FIXME: same as 'type' for files, and empty string for folders
'publish_date', // empty string for folders
'children' // undefined for files, a (possibly empty) array for folders
],
/**
* Returns the path of the file, starting with root.
*/
getPathArray: function() {
var me = this;
// Skip the root and the 1st level folder, which refers to the project itself.
if ("root" == me.get('id')) return [];
if ("root" == me.get('parentId')) return [];
var fileType = me.get('type');
var parentId = me.get('parentId');
if (!parentId) return [me.get('name')];
var fileStore = Ext.StoreManager.get('Files');
var parent = fileStore.getById(parentId);
if (!parent) return [me.get('name')];
var parentPath = parent.getPathArray();
// For a file return just the path (=parentPath)
if ("folder" != fileType) return parentPath;
// For a folder, add the folder name to the path
parentPath.push(me.get('name'));
return parentPath;
}
});
\ No newline at end of file
/*
* Data related to a local file.
*
* This data is provided by the electron-tree module.
*/
Ext.define('POSync.model.LocalFile', {
extend: 'Ext.data.TreeModel',
idProperty: 'path',
fields: [
'path', // The full path of the file or directory
'name', // The name (with extension) of the file or directory
'extension', // The extension of the file or directory
'size', // The size in bytes
'type', // Either 'directory' or 'file'
'children', // List of files and subdirectories (for directories only)
// Added for internal sencha management
'expandable', // true if directory contents are shown
'expanded', // true if directory contents are shown
'leaf', // true if node has no children
'sha1',
'date',
'mime_type',
'iconCls'
],
proxy: {
type: 'memory',
reader: {
type: 'json',
root: 'children'
}
}
});
/*
* What should we do during sync?
*/
Ext.define('POSync.model.Operation', {
extend: 'Ext.data.Model',
idProperty: 'id',
fields: [
'id',
'file_id',
'file_path',
'file_name',
'mime_type',
'file_sha1',
'op'
]
});
\ No newline at end of file
/*
* Data related to a project.
*
* Only the fields required by this application are specified here.
*/
Ext.define('POSync.model.Project', {
extend: 'Ext.data.Model',
idProperty: 'project_id',
fields: [
'id',
'project_id', // The primary key or object_id of the project
'project_name', // The name of the project
'creation_user', // User_id of the guy creating the project
'project_nr', // The short name of the project.
'project_path', // The short name of the project.
'project_wbs', // WBS code
'parent_id', // The parent of the project or NULL for a main project
'tree_sortkey', // A strange bitstring that determines the hierarchical position
'company_id', // Company for whom the project has been created
'project_status_id', // 76=open, 81=closed, ...
'project_type_id', // 100=Task, 101=Ticket, 2501=Gantt Project, ...
'start_date', // '2001-01-01 00:00:00+01'
'end_date',
'project_lead_id', // Project manager
'percent_completed', // 0 - 100: Defines what has already been done.
'description',
'note',
{ /* used to display in selection boxes */
name: 'full_name',
convert: function (value, record) {
return record.get('project_nr') + ' - ' + record.get('project_name');
}
}
]
});
This diff is collapsed.
{
"name": "posync",
"version": "1.0.0",
"description": "Primera prueba con electron.",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"files": [
"index.html",
"app.js",
"ext",
"images",
"topdir"
],
"author": "salo",
"license": "ISC",
"devDependencies": {
"electron": "^2.0.8"
},
"dependencies": {
"directory-tree": "^2.1.0",
"electron-store": "^2.0.0",
"file-type": "^10.4.0"
}
}
/*
* DataStore for files.
*
* This is a read-only store.
* Proxy URL depends on application's configuration settings.
*/
Ext.define('POSync.store.Files', {
storeId: 'Files',
extend: 'Ext.data.TreeStore',
requires: 'POSync.model.File',
model: 'POSync.model.File',
nodeParam: 'object_id',
root: {
expanded: true,
children: []
},
proxy: {
type: 'ajax',
url: '/intranet-rest-fs-openacs/file-tree.json',
reader: {
type: 'json',
root: 'children'
}
}
});
/*
* DataStore for local files.
*
* Proxy URL depends on application's configuration settings.
*/
Ext.define('POSync.store.LocalFiles', {
extend: 'Ext.data.TreeStore',
requires: 'POSync.model.LocalFile',
model: 'POSync.model.LocalFile',
isLoaded: null, // ID of project loaded
root: {
expanded: true,
children: []
},
/* Load the store with the specified tree */
loadTree: function (tree) {
var proxy = this.getProxy();
proxy.data = tree;
this.load();
},
/**
* Check if a file with the given path exists
*/
searchUsingPathArray: function(pathArray) {
var me = this;
var root = me.getRootNode();
// console.log(root);
// Iterate through the pathArray elements from
// the top to find the file in the local folder
var node = root;
for (var i = 0; i < pathArray.length; i++) {
var path = pathArray[i];
// Check all children
var child = null;
var children = node.children;
if (!children) return null;
for (var j = 0; j < children.length; j++) {
var childName = children[j].get('name');
if (path == childName) {
child = children[j];
}
}
if (!child) return null;
node = child;
}
}
});
/*
* DataStore for Sync operations
*/
Ext.define('POSync.store.Operations', {
storeId: 'operations',
extend: 'Ext.data.Store',
requires: 'POSync.model.Operation',
model: 'POSync.model.Operation'
});
/*
* DataStore for projects.
*
* This is a read-only store.
* Proxy URL depends on application's configuration settings.
*/
Ext.define('POSync.store.Projects', {
storeId: 'Projects',
extend: 'Ext.data.Store',
model: 'POSync.model.Project',
autoLoad: false, // Allow the application to set extraParams
remoteFilter: true, // Do not filter on the Sencha side
pageSize: 100000, // Load all projects, no matter what size(?)
isLoaded: false,
proxy: {
type: 'rest', // Standard ]po[ REST interface for loading
url: '/intranet-rest/im_project',
appendId: true,
timeout: 300000,
extraParams: {
format: 'json',
query: 'parent_id is NULL' // Select only top-level projects
// deref_p: '1' // We don't need company_name etc.
// This should be overwrittten during load.
},
reader: {
type: 'json', // Tell the Proxy Reader to parse JSON
root: 'data', // Where do the data start in the JSON file?
totalProperty: 'total' // Total number of tickets for pagination
},
writer: {
type: 'json' // Allow Sencha to write ticket changes
}
}
});
Ext.define('POSync.view.LoginPanel', {
alias: 'loginPanel',
extend: 'Ext.panel.Panel',
title: 'Configuration',
items: {
xtype: 'form',
itemId: 'config',
bodyPadding: 10,
defaults: {
anchor: '100%',
xtype: 'textfield',
allowBlank: false
},
items: [{
name: 'url',
inputType: 'url',
// vtype: 'url',
regexp: '/(((^https?)|(^ftp)):\/\/((([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*)|(localhost|LOCALHOST))\/?)/',
fieldLabel: 'Server URL',
emptyText: 'Enter here the URL of ]project-open[ server',
}, {
name: 'email',
inputType: 'email',
vtype: 'email',
fieldLabel: 'Email',
emptyText: 'Enter here the email to use as login name',
}, {
name: 'password',
inputType: 'password',
fieldLabel: 'Password',
emptyText: 'Enter here your login password',
}, {
name: 'topdir',
xtype: 'dirfield',
fieldLabel: 'Local directory',
/* FIXME: without an emptyText renders to height zero */
emptyText: 'Click the arrow to select the local directory to sync',
}]
}
});
// Currently not used, because moving it creates an error
Ext.define('POSync.view.ProjectsPanel', {
alias: 'projectsPanel',
extend: 'Ext.panel.Panel',
title: 'Projects',
items: {
xtype: 'grid',
itemId: 'projects',
store: 'Projects',
/* FIXME: emptyText initially not shown */
emptyText: 'No projects available',
columns: [{
text: 'Id',
dataIndex: 'project_id',
align: 'right',
flex: 1
}, {
text: 'Number',
dataIndex: 'project_nr',
flex: 2
}, {
text: 'Name',
dataIndex: 'project_name',
flex: 7
}]
}
});
......@@ -9,8 +9,8 @@ ad_page_contract {
@author Frank Bergmann
@cvs-id $Id$
} {
{ file_id "" }
{ version_id "" }
{ file_id:integer "" }
{ version_id:integer "" }
}
if {"" eq $file_id && "" ne $version_id} {
......@@ -31,13 +31,15 @@ set content_root [fs::get_root_folder]
set template_root [db_string template_root "select content_template__get_root_folder()"]
set user_id [ad_conn user_id]
set storage_area_key "CR_FILES"
set path [cr_fs_path $storage_area_key]
set filename [db_string filename "select :path || content from cr_revisions where revision_id = :version_id"]
set filename [db_string filename "select :path || content from cr_revisions where revision_id = :version_id" -default ""]
if {"" eq $filename} {
# version_id not found
doc_return 404 "text/plain" "Not found"
ad_script_abort
}
set binary_content ""
if {[catch {
......@@ -51,7 +53,7 @@ if {[catch {
}
ad_return_complaint 1 "$path - $filename - len=[string length $binary_content] - sha1=[ns_sha1 $binary_content]"
# ad_return_complaint 1 "$path - $filename - len=[string length $binary_content] - sha1=[ns_sha1 $binary_content]"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment