Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
J
jio
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Xiaowu Zhang
jio
Commits
2fe3ea44
Commit
2fe3ea44
authored
Aug 21, 2013
by
Tristan Cavelier
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
all storage removed except localstorage
parent
3356688e
Changes
13
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
0 additions
and
8965 deletions
+0
-8965
src/jio.storage/conflictmanagerstorage.js
src/jio.storage/conflictmanagerstorage.js
+0
-1085
src/jio.storage/cryptstorage.js
src/jio.storage/cryptstorage.js
+0
-277
src/jio.storage/davstorage.js
src/jio.storage/davstorage.js
+0
-943
src/jio.storage/erp5storage.js
src/jio.storage/erp5storage.js
+0
-457
src/jio.storage/gidstorage.js
src/jio.storage/gidstorage.js
+0
-615
src/jio.storage/indexstorage.js
src/jio.storage/indexstorage.js
+0
-982
src/jio.storage/ramstorage.js
src/jio.storage/ramstorage.js
+0
-446
src/jio.storage/replicaterevisionstorage.js
src/jio.storage/replicaterevisionstorage.js
+0
-692
src/jio.storage/replicatestorage.js
src/jio.storage/replicatestorage.js
+0
-110
src/jio.storage/revisionstorage.js
src/jio.storage/revisionstorage.js
+0
-1034
src/jio.storage/s3storage.js
src/jio.storage/s3storage.js
+0
-1005
src/jio.storage/splitstorage.js
src/jio.storage/splitstorage.js
+0
-588
src/jio.storage/xwikistorage.js
src/jio.storage/xwikistorage.js
+0
-731
No files found.
src/jio.storage/conflictmanagerstorage.js
deleted
100644 → 0
View file @
3356688e
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global jIO: true, hex_sha256: true, setTimeout: true */
jIO
.
addStorageType
(
'
conflictmanager
'
,
function
(
spec
,
my
)
{
var
that
,
priv
,
storage_exists
,
local_namespace
,
empty_fun
,
super_serialized
;
spec
=
spec
||
{};
that
=
my
.
basicStorage
(
spec
,
my
);
priv
=
{};
storage_exists
=
(
spec
.
storage
?
true
:
false
);
priv
.
sub_storage_spec
=
spec
.
storage
||
{
type
:
'
base
'
};
priv
.
sub_storage_string
=
JSON
.
stringify
(
priv
.
sub_storage_spec
);
local_namespace
=
'
jio/conflictmanager/
'
+
priv
.
sub_storage_string
+
'
/
'
;
empty_fun
=
function
()
{};
super_serialized
=
that
.
serialized
;
that
.
serialized
=
function
()
{
var
o
=
super_serialized
();
o
.
storage
=
priv
.
sub_storage_spec
;
return
o
;
};
that
.
validateState
=
function
()
{
if
(
storage_exists
)
{
return
''
;
}
return
'
Need at least one parameter: "storage".
'
;
};
priv
.
getDistantMetadata
=
function
(
command
,
path
,
success
,
error
)
{
var
cloned_option
=
command
.
cloneOption
();
cloned_option
.
metadata_only
=
false
;
that
.
addJob
(
'
get
'
,
priv
.
sub_storage_spec
,
path
,
cloned_option
,
success
,
error
);
};
priv
.
saveMetadataToDistant
=
function
(
command
,
path
,
content
,
success
,
error
)
{
that
.
addJob
(
'
put
'
,
priv
.
sub_storage_spec
,
{
_id
:
path
,
content
:
JSON
.
stringify
(
content
)
},
command
.
cloneOption
(),
success
,
error
);
};
priv
.
saveNewRevision
=
function
(
command
,
path
,
content
,
success
,
error
)
{
that
.
addJob
(
'
post
'
,
priv
.
sub_storage_spec
,
{
_id
:
path
,
content
:
content
},
command
.
cloneOption
(),
success
,
error
);
};
priv
.
loadRevision
=
function
(
command
,
path
,
success
,
error
)
{
that
.
addJob
(
'
get
'
,
priv
.
sub_storage_spec
,
path
,
command
.
cloneOption
(),
success
,
error
);
};
priv
.
deleteAFile
=
function
(
command
,
path
,
success
,
error
)
{
that
.
addJob
(
'
remove
'
,
priv
.
sub_storage_spec
,
{
_id
:
path
},
command
.
cloneOption
(),
success
,
error
);
};
priv
.
chooseARevision
=
function
(
metadata
)
{
var
tmp_last_modified
=
0
,
ret_rev
=
''
,
rev
;
for
(
rev
in
metadata
)
{
if
(
metadata
.
hasOwnProperty
(
rev
))
{
if
(
tmp_last_modified
<
metadata
[
rev
].
_last_modified
)
{
tmp_last_modified
=
metadata
[
rev
].
_last_modified
;
ret_rev
=
rev
;
}
}
}
return
ret_rev
;
};
priv
.
_revs
=
function
(
metadata
,
revision
)
{
if
(
!
(
metadata
&&
revision
))
{
return
null
;
}
if
(
metadata
[
revision
])
{
return
{
start
:
metadata
[
revision
].
_revisions
.
length
,
ids
:
metadata
[
revision
].
_revisions
};
}
return
null
;
};
priv
.
_revs_info
=
function
(
metadata
)
{
if
(
!
metadata
)
{
return
null
;
}
var
k
,
l
=
[];
for
(
k
in
metadata
)
{
if
(
metadata
.
hasOwnProperty
(
k
))
{
l
.
push
({
rev
:
k
,
status
:
(
metadata
[
k
]
?
(
metadata
[
k
].
_deleted
?
'
deleted
'
:
'
available
'
)
:
'
missing
'
)
});
}
}
return
l
;
};
priv
.
solveConflict
=
function
(
doc
,
option
,
param
)
{
var
o
=
{},
am
=
priv
.
newAsyncModule
(),
command
=
param
.
command
,
metadata_file_path
=
param
.
docid
+
'
.metadata
'
,
current_revision
=
''
,
current_revision_file_path
=
''
,
metadata_file_content
=
null
,
on_conflict
=
false
,
conflict_object
=
{
total_rows
:
0
,
rows
:
[]
},
on_remove
=
param
.
_deleted
,
previous_revision
=
param
.
previous_revision
,
previous_revision_content_object
=
null
,
now
=
new
Date
(),
failerror
;
o
.
getDistantMetadata
=
function
()
{
priv
.
getDistantMetadata
(
command
,
metadata_file_path
,
function
(
result
)
{
var
previous_revision_number
=
parseInt
(
previous_revision
.
split
(
'
-
'
)[
0
],
10
);
metadata_file_content
=
JSON
.
parse
(
result
.
content
);
// set current revision
// jslint: removed '' in hex_sha256(''...
current_revision
=
(
previous_revision_number
+
1
)
+
'
-
'
+
hex_sha256
(
doc
.
content
+
previous_revision
+
JSON
.
stringify
(
metadata_file_content
));
current_revision_file_path
=
param
.
docid
+
'
.
'
+
current_revision
;
previous_revision_content_object
=
metadata_file_content
[
previous_revision
]
||
{};
if
(
!
on_remove
)
{
am
.
wait
(
o
,
'
saveMetadataOnDistant
'
,
1
);
am
.
call
(
o
,
'
saveNewRevision
'
);
}
am
.
call
(
o
,
'
previousUpdateMetadata
'
);
},
function
(
error
)
{
am
.
call
(
o
,
'
error
'
,
[
error
]);
}
);
};
o
.
saveNewRevision
=
function
()
{
priv
.
saveNewRevision
(
command
,
current_revision_file_path
,
doc
.
content
,
function
()
{
am
.
call
(
o
,
'
saveMetadataOnDistant
'
);
},
function
(
error
)
{
am
.
call
(
o
,
'
error
'
,
[
error
]);
}
);
};
o
.
previousUpdateMetadata
=
function
()
{
var
i
;
for
(
i
=
0
;
i
<
param
.
key
.
length
;
i
+=
1
)
{
delete
metadata_file_content
[
param
.
key
[
i
]];
}
am
.
call
(
o
,
'
checkForConflicts
'
);
};
o
.
checkForConflicts
=
function
()
{
var
rev
;
for
(
rev
in
metadata_file_content
)
{
if
(
metadata_file_content
.
hasOwnProperty
(
rev
))
{
on_conflict
=
true
;
failerror
=
{
status
:
409
,
error
:
'
conflict
'
,
statusText
:
'
Conflict
'
,
reason
:
'
document update conflict
'
,
message
:
'
There is one or more conflicts
'
};
break
;
}
}
am
.
call
(
o
,
'
updateMetadata
'
);
};
o
.
updateMetadata
=
function
()
{
var
revision_history
,
id
=
''
;
id
=
current_revision
.
split
(
'
-
'
);
id
.
shift
();
id
=
id
.
join
(
'
-
'
);
revision_history
=
previous_revision_content_object
.
_revisions
;
revision_history
.
unshift
(
id
);
metadata_file_content
[
current_revision
]
=
{
_creation_date
:
previous_revision_content_object
.
_creation_date
||
now
.
getTime
(),
_last_modified
:
now
.
getTime
(),
_revisions
:
revision_history
,
_conflict
:
on_conflict
,
_deleted
:
on_remove
};
if
(
on_conflict
)
{
conflict_object
=
priv
.
createConflictObject
(
command
,
metadata_file_content
,
current_revision
);
}
am
.
call
(
o
,
'
saveMetadataOnDistant
'
);
};
o
.
saveMetadataOnDistant
=
function
()
{
priv
.
saveMetadataToDistant
(
command
,
metadata_file_path
,
metadata_file_content
,
function
()
{
am
.
call
(
o
,
'
deleteAllConflictingRevision
'
);
if
(
on_conflict
)
{
am
.
call
(
o
,
'
error
'
);
}
else
{
am
.
call
(
o
,
'
success
'
);
}
},
function
(
error
)
{
am
.
call
(
o
,
'
error
'
,
[
error
]);
}
);
};
o
.
deleteAllConflictingRevision
=
function
()
{
var
i
;
for
(
i
=
0
;
i
<
param
.
key
.
length
;
i
+=
1
)
{
priv
.
deleteAFile
(
command
,
param
.
docid
+
'
.
'
+
param
.
key
[
i
],
empty_fun
,
empty_fun
);
}
};
o
.
success
=
function
()
{
var
a
=
{
ok
:
true
,
id
:
param
.
docid
,
rev
:
current_revision
};
am
.
neverCall
(
o
,
'
error
'
);
am
.
neverCall
(
o
,
'
success
'
);
if
(
option
.
revs
)
{
a
.
revisions
=
priv
.
_revs
(
metadata_file_content
,
current_revision
);
}
if
(
option
.
revs_info
)
{
a
.
revs_info
=
priv
.
_revs_info
(
metadata_file_content
);
}
if
(
option
.
conflicts
)
{
a
.
conflicts
=
conflict_object
;
}
param
.
success
(
a
);
};
o
.
error
=
function
(
error
)
{
var
err
=
error
||
failerror
||
{
status
:
0
,
statusText
:
'
Unknown
'
,
error
:
'
unknown_error
'
,
message
:
'
Unknown error.
'
,
reason
:
'
unknown error
'
};
if
(
current_revision
)
{
err
.
rev
=
current_revision
;
}
if
(
option
.
revs
)
{
err
.
revisions
=
priv
.
_revs
(
metadata_file_content
,
current_revision
);
}
if
(
option
.
revs_info
)
{
err
.
revs_info
=
priv
.
_revs_info
(
metadata_file_content
);
}
if
(
option
.
conflicts
)
{
err
.
conflicts
=
conflict_object
;
}
am
.
neverCall
(
o
,
'
error
'
);
am
.
neverCall
(
o
,
'
success
'
);
param
.
error
(
err
);
};
am
.
call
(
o
,
'
getDistantMetadata
'
);
};
priv
.
createConflictObject
=
function
(
command
,
metadata
,
revision
)
{
return
{
total_rows
:
1
,
rows
:
[
priv
.
createConflictRow
(
command
,
command
.
getDocId
(),
metadata
,
revision
)]
};
};
priv
.
getParam
=
function
(
list
)
{
var
param
=
{},
i
=
0
;
if
(
typeof
list
[
i
]
===
'
string
'
)
{
param
.
content
=
list
[
i
];
i
+=
1
;
}
if
(
typeof
list
[
i
]
===
'
object
'
)
{
param
.
options
=
list
[
i
];
i
+=
1
;
}
else
{
param
.
options
=
{};
}
param
.
callback
=
function
()
{};
param
.
success
=
function
(
val
)
{
param
.
callback
(
undefined
,
val
);
};
param
.
error
=
function
(
err
)
{
param
.
callback
(
err
,
undefined
);
};
if
(
typeof
list
[
i
]
===
'
function
'
)
{
if
(
typeof
list
[
i
+
1
]
===
'
function
'
)
{
param
.
success
=
list
[
i
];
param
.
error
=
list
[
i
+
1
];
}
else
{
param
.
callback
=
list
[
i
];
}
}
return
param
;
};
priv
.
createConflictRow
=
function
(
command
,
docid
,
metadata
,
revision
)
{
var
row
=
{
id
:
docid
,
key
:
[],
value
:
{
// jslint: removed params /* content, option, success, error */
_solveConflict
:
function
()
{
var
param
=
{},
got
=
priv
.
getParam
(
arguments
);
if
(
got
.
content
===
undefined
)
{
param
.
_deleted
=
true
;
}
else
{
param
.
_deleted
=
false
;
}
param
.
success
=
got
.
success
;
param
.
error
=
got
.
error
;
param
.
previous_revision
=
revision
;
param
.
docid
=
docid
;
param
.
key
=
row
.
key
;
param
.
command
=
command
.
clone
();
return
priv
.
solveConflict
({
_id
:
docid
,
content
:
got
.
content
,
_rev
:
revision
},
got
.
options
,
param
);
}
}
},
k
;
for
(
k
in
metadata
)
{
if
(
metadata
.
hasOwnProperty
(
k
))
{
row
.
key
.
push
(
k
);
}
}
return
row
;
};
priv
.
newAsyncModule
=
function
()
{
var
async
=
{};
async
.
call
=
function
(
obj
,
function_name
,
arglist
)
{
obj
.
_wait
=
obj
.
_wait
||
{};
if
(
obj
.
_wait
[
function_name
])
{
obj
.
_wait
[
function_name
]
-=
1
;
return
empty_fun
;
}
// ok if undef or 0
arglist
=
arglist
||
[];
setTimeout
(
function
()
{
obj
[
function_name
].
apply
(
obj
[
function_name
],
arglist
);
});
};
async
.
neverCall
=
function
(
obj
,
function_name
)
{
obj
.
_wait
=
obj
.
_wait
||
{};
obj
.
_wait
[
function_name
]
=
-
1
;
};
async
.
wait
=
function
(
obj
,
function_name
,
times
)
{
obj
.
_wait
=
obj
.
_wait
||
{};
obj
.
_wait
[
function_name
]
=
times
;
};
async
.
end
=
function
()
{
async
.
call
=
empty_fun
;
};
return
async
;
};
that
.
post
=
function
(
command
)
{
that
.
put
(
command
);
};
/**
* Save a document and can manage conflicts.
* @method put
*/
that
.
put
=
function
(
command
)
{
var
o
=
{},
am
=
priv
.
newAsyncModule
(),
metadata_file_path
=
command
.
getDocId
()
+
'
.metadata
'
,
current_revision
=
''
,
current_revision_file_path
=
''
,
metadata_file_content
=
null
,
on_conflict
=
false
,
conflict_object
=
{
total_rows
:
0
,
rows
:
[]
},
previous_revision
=
command
.
getDocInfo
(
'
_rev
'
)
||
'
0
'
,
previous_revision_file_path
=
command
.
getDocId
()
+
'
.
'
+
previous_revision
,
now
=
new
Date
(),
failerror
;
o
.
getDistantMetadata
=
function
()
{
priv
.
getDistantMetadata
(
command
,
metadata_file_path
,
function
(
result
)
{
var
previous_revision_number
=
parseInt
(
previous_revision
.
split
(
'
-
'
)[
0
],
10
);
metadata_file_content
=
JSON
.
parse
(
result
.
content
);
// set current revision
current_revision
=
(
previous_revision_number
+
1
)
+
'
-
'
+
// jslint: removed hex_sha256(''+...
hex_sha256
(
command
.
getDocContent
()
+
previous_revision
+
JSON
.
stringify
(
metadata_file_content
));
current_revision_file_path
=
command
.
getDocId
()
+
'
.
'
+
current_revision
;
am
.
wait
(
o
,
'
saveMetadataOnDistant
'
,
1
);
am
.
call
(
o
,
'
saveNewRevision
'
);
am
.
call
(
o
,
'
checkForConflicts
'
);
},
function
(
error
)
{
if
(
error
.
status
===
404
)
{
current_revision
=
'
1-
'
+
hex_sha256
(
command
.
getDocContent
());
current_revision_file_path
=
command
.
getDocId
()
+
'
.
'
+
current_revision
;
am
.
wait
(
o
,
'
saveMetadataOnDistant
'
,
1
);
am
.
call
(
o
,
'
saveNewRevision
'
);
am
.
call
(
o
,
'
createMetadata
'
);
}
else
{
am
.
call
(
o
,
'
error
'
,
[
error
]);
}
}
);
};
o
.
saveNewRevision
=
function
()
{
priv
.
saveNewRevision
(
command
,
current_revision_file_path
,
command
.
getDocContent
(),
function
()
{
am
.
call
(
o
,
'
saveMetadataOnDistant
'
);
},
function
(
error
)
{
am
.
call
(
o
,
'
error
'
,
[
error
]);
}
);
};
o
.
checkForConflicts
=
function
()
{
var
rev
;
for
(
rev
in
metadata_file_content
)
{
if
(
metadata_file_content
.
hasOwnProperty
(
rev
)
&&
rev
!==
previous_revision
)
{
on_conflict
=
true
;
failerror
=
{
status
:
409
,
error
:
'
conflict
'
,
statusText
:
'
Conflict
'
,
reason
:
'
document update conflict
'
,
message
:
'
Document update conflict.
'
};
break
;
}
}
am
.
call
(
o
,
'
updateMetadata
'
);
};
o
.
createMetadata
=
function
()
{
var
id
=
current_revision
;
id
=
id
.
split
(
'
-
'
);
id
.
shift
();
id
=
id
.
join
(
'
-
'
);
metadata_file_content
=
{};
metadata_file_content
[
current_revision
]
=
{
_creation_date
:
now
.
getTime
(),
_last_modified
:
now
.
getTime
(),
_revisions
:
[
id
],
_conflict
:
false
,
_deleted
:
false
};
am
.
call
(
o
,
'
saveMetadataOnDistant
'
);
};
o
.
updateMetadata
=
function
()
{
var
previous_creation_date
,
revision_history
=
[],
id
=
''
;
if
(
metadata_file_content
[
previous_revision
])
{
previous_creation_date
=
metadata_file_content
[
previous_revision
].
_creation_date
;
revision_history
=
metadata_file_content
[
previous_revision
].
_revisions
;
delete
metadata_file_content
[
previous_revision
];
}
id
=
current_revision
.
split
(
'
-
'
);
id
.
shift
();
id
=
id
.
join
(
'
-
'
);
revision_history
.
unshift
(
id
);
metadata_file_content
[
current_revision
]
=
{
_creation_date
:
previous_creation_date
||
now
.
getTime
(),
_last_modified
:
now
.
getTime
(),
_revisions
:
revision_history
,
_conflict
:
on_conflict
,
_deleted
:
false
};
if
(
on_conflict
)
{
conflict_object
=
priv
.
createConflictObject
(
command
,
metadata_file_content
,
current_revision
);
}
am
.
call
(
o
,
'
saveMetadataOnDistant
'
);
};
o
.
saveMetadataOnDistant
=
function
()
{
priv
.
saveMetadataToDistant
(
command
,
metadata_file_path
,
metadata_file_content
,
function
()
{
am
.
call
(
o
,
'
deletePreviousRevision
'
);
if
(
on_conflict
)
{
am
.
call
(
o
,
'
error
'
);
}
else
{
am
.
call
(
o
,
'
success
'
);
}
},
function
(
error
)
{
am
.
call
(
o
,
'
error
'
,
[
error
]);
}
);
};
o
.
deletePreviousRevision
=
function
()
{
// jslint: removed /*&& !on_conflict*/
if
(
previous_revision
!==
'
0
'
)
{
priv
.
deleteAFile
(
command
,
previous_revision_file_path
,
empty_fun
,
empty_fun
);
}
};
o
.
success
=
function
()
{
var
a
=
{
ok
:
true
,
id
:
command
.
getDocId
(),
rev
:
current_revision
};
am
.
neverCall
(
o
,
'
error
'
);
am
.
neverCall
(
o
,
'
success
'
);
if
(
command
.
getOption
(
'
revs
'
))
{
a
.
revisions
=
priv
.
_revs
(
metadata_file_content
,
current_revision
);
}
if
(
command
.
getOption
(
'
revs_info
'
))
{
a
.
revs_info
=
priv
.
_revs_info
(
metadata_file_content
);
}
if
(
command
.
getOption
(
'
conflicts
'
))
{
a
.
conflicts
=
conflict_object
;
}
that
.
success
(
a
);
};
o
.
error
=
function
(
error
)
{
var
err
=
error
||
failerror
||
{
status
:
0
,
statusText
:
'
Unknown
'
,
error
:
'
unknown_error
'
,
message
:
'
Unknown error.
'
,
reason
:
'
unknown error
'
};
if
(
current_revision
)
{
err
.
rev
=
current_revision
;
}
if
(
command
.
getOption
(
'
revs
'
))
{
err
.
revisions
=
priv
.
_revs
(
metadata_file_content
,
current_revision
);
}
if
(
command
.
getOption
(
'
revs_info
'
))
{
err
.
revs_info
=
priv
.
_revs_info
(
metadata_file_content
);
}
if
(
command
.
getOption
(
'
conflicts
'
))
{
err
.
conflicts
=
conflict_object
;
}
am
.
neverCall
(
o
,
'
error
'
);
am
.
neverCall
(
o
,
'
success
'
);
that
.
error
(
err
);
};
am
.
call
(
o
,
'
getDistantMetadata
'
);
};
// end put
/**
* Load a document from several storages, and send the first retreived
* document.
* @method get
*/
that
.
get
=
function
(
command
)
{
var
o
=
{},
am
=
priv
.
newAsyncModule
(),
metadata_file_path
=
command
.
getDocId
()
+
'
.metadata
'
,
current_revision
=
command
.
getOption
(
'
rev
'
)
||
''
,
metadata_file_content
=
null
,
metadata_only
=
command
.
getOption
(
'
metadata_only
'
),
on_conflict
=
false
,
conflict_object
=
{
total_rows
:
0
,
rows
:
[]
},
doc
=
{
_id
:
command
.
getDocId
()
},
call404
=
function
(
message
)
{
am
.
call
(
o
,
'
error
'
,
[{
status
:
404
,
statusText
:
'
Not Found
'
,
error
:
'
not_found
'
,
message
:
message
,
reason
:
message
}]);
};
o
.
getDistantMetadata
=
function
()
{
priv
.
getDistantMetadata
(
command
,
metadata_file_path
,
function
(
result
)
{
metadata_file_content
=
JSON
.
parse
(
result
.
content
);
if
(
!
metadata_only
)
{
am
.
wait
(
o
,
'
success
'
,
1
);
}
am
.
call
(
o
,
'
affectMetadata
'
);
am
.
call
(
o
,
'
checkForConflicts
'
);
},
function
(
error
)
{
am
.
call
(
o
,
'
error
'
,
[
error
]);
}
);
};
o
.
affectMetadata
=
function
()
{
if
(
current_revision
)
{
if
(
!
metadata_file_content
[
current_revision
])
{
return
call404
(
'
Document revision does not exists.
'
);
}
}
else
{
current_revision
=
priv
.
chooseARevision
(
metadata_file_content
);
}
doc
.
_last_modified
=
metadata_file_content
[
current_revision
].
_last_modified
;
doc
.
_creation_date
=
metadata_file_content
[
current_revision
].
_creation_date
;
doc
.
_rev
=
current_revision
;
if
(
metadata_only
)
{
am
.
call
(
o
,
'
success
'
);
}
else
{
am
.
call
(
o
,
'
loadRevision
'
);
}
};
o
.
loadRevision
=
function
()
{
if
(
!
current_revision
||
metadata_file_content
[
current_revision
].
_deleted
)
{
return
call404
(
'
Document has been removed.
'
);
}
priv
.
loadRevision
(
command
,
doc
.
_id
+
'
.
'
+
current_revision
,
function
(
result
)
{
doc
.
content
=
result
.
content
;
am
.
call
(
o
,
'
success
'
);
},
function
(
error
)
{
am
.
call
(
o
,
'
error
'
,
[
error
]);
}
);
};
o
.
checkForConflicts
=
function
()
{
if
(
metadata_file_content
[
current_revision
].
_conflict
)
{
on_conflict
=
true
;
conflict_object
=
priv
.
createConflictObject
(
command
,
metadata_file_content
,
current_revision
);
}
am
.
call
(
o
,
'
success
'
);
};
o
.
success
=
function
()
{
am
.
neverCall
(
o
,
'
error
'
);
am
.
neverCall
(
o
,
'
success
'
);
if
(
command
.
getOption
(
'
revs
'
))
{
doc
.
_revisions
=
priv
.
_revs
(
metadata_file_content
,
current_revision
);
}
if
(
command
.
getOption
(
'
revs_info
'
))
{
doc
.
_revs_info
=
priv
.
_revs_info
(
metadata_file_content
);
}
if
(
command
.
getOption
(
'
conflicts
'
))
{
doc
.
_conflicts
=
conflict_object
;
}
that
.
success
(
doc
);
};
o
.
error
=
function
(
error
)
{
var
err
=
error
||
{
status
:
0
,
statusText
:
'
Unknown
'
,
message
:
'
Unknown error.
'
};
if
(
command
.
getOption
(
'
revs
'
))
{
err
.
_revisions
=
priv
.
_revs
(
metadata_file_content
,
current_revision
);
}
if
(
command
.
getOption
(
'
revs_info
'
))
{
err
.
_revs_info
=
priv
.
_revs_info
(
metadata_file_content
);
}
if
(
command
.
getOption
(
'
conflicts
'
))
{
err
.
_conflicts
=
conflict_object
;
}
am
.
neverCall
(
o
,
'
error
'
);
am
.
neverCall
(
o
,
'
success
'
);
that
.
error
(
err
);
};
am
.
call
(
o
,
'
getDistantMetadata
'
);
};
/**
* Get a document list from several storages, and returns the first
* retreived document list.
* @method allDocs
*/
that
.
allDocs
=
function
(
command
)
{
var
o
=
{},
am
=
priv
.
newAsyncModule
(),
metadata_only
=
command
.
getOption
(
'
metadata_only
'
),
result_list
=
[],
conflict_object
=
{
total_rows
:
0
,
rows
:
[]
},
success_count
=
0
,
success_max
=
0
;
o
.
retreiveList
=
function
()
{
var
cloned_option
=
command
.
cloneOption
(),
success
=
function
(
result
)
{
am
.
call
(
o
,
'
filterTheList
'
,
[
result
]);
},
error
=
function
(
error
)
{
am
.
call
(
o
,
'
error
'
,
[
error
]);
};
cloned_option
.
metadata_only
=
true
;
that
.
addJob
(
'
allDocs
'
,
priv
.
sub_storage_spec
,
null
,
cloned_option
,
success
,
error
);
};
o
.
filterTheList
=
function
(
result
)
{
var
i
,
splitname
;
success_max
+=
1
;
for
(
i
=
0
;
i
<
result
.
total_rows
;
i
+=
1
)
{
splitname
=
result
.
rows
[
i
].
id
.
split
(
'
.
'
)
||
[];
if
(
splitname
.
length
>
0
&&
splitname
[
splitname
.
length
-
1
]
===
'
metadata
'
)
{
success_max
+=
1
;
splitname
.
length
-=
1
;
am
.
call
(
o
,
'
loadMetadataFile
'
,
[
splitname
.
join
(
'
.
'
)]);
}
}
am
.
call
(
o
,
'
success
'
);
};
o
.
loadMetadataFile
=
function
(
path
)
{
priv
.
getDistantMetadata
(
command
,
path
+
'
.metadata
'
,
function
(
data
)
{
data
=
JSON
.
parse
(
data
.
content
);
var
revision
=
priv
.
chooseARevision
(
data
);
if
(
!
data
[
revision
].
_deleted
)
{
am
.
call
(
o
,
'
loadFile
'
,
[
path
,
revision
,
data
]);
}
else
{
am
.
call
(
o
,
'
success
'
);
}
},
function
(
error
)
{
am
.
call
(
o
,
'
error
'
,
[
error
]);
}
);
};
o
.
loadFile
=
function
(
path
,
revision
,
data
)
{
var
doc
=
{
id
:
path
,
key
:
path
,
value
:
{
_last_modified
:
data
[
revision
].
_last_modified
,
_creation_date
:
data
[
revision
].
_creation_date
,
_rev
:
revision
}
};
if
(
command
.
getOption
(
'
revs
'
))
{
doc
.
value
.
_revisions
=
priv
.
_revs
(
data
,
revision
);
}
if
(
command
.
getOption
(
'
revs_info
'
))
{
doc
.
value
.
_revs_info
=
priv
.
_revs_info
(
data
,
revision
);
}
if
(
command
.
getOption
(
'
conflicts
'
))
{
if
(
data
[
revision
].
_conflict
)
{
conflict_object
.
total_rows
+=
1
;
conflict_object
.
rows
.
push
(
priv
.
createConflictRow
(
command
,
path
,
data
,
revision
)
);
}
}
if
(
!
metadata_only
)
{
priv
.
loadRevision
(
command
,
path
+
'
.
'
+
revision
,
function
(
data
)
{
doc
.
content
=
data
.
content
;
result_list
.
push
(
doc
);
am
.
call
(
o
,
'
success
'
);
},
function
(
error
)
{
am
.
call
(
o
,
'
error
'
,
[
error
]);
}
);
}
else
{
result_list
.
push
(
doc
);
am
.
call
(
o
,
'
success
'
);
}
};
o
.
success
=
function
()
{
var
obj
;
success_count
+=
1
;
if
(
success_count
>=
success_max
)
{
am
.
end
();
obj
=
{
total_rows
:
result_list
.
length
,
rows
:
result_list
};
if
(
command
.
getOption
(
'
conflicts
'
))
{
obj
.
conflicts
=
conflict_object
;
}
that
.
success
(
obj
);
}
};
o
.
error
=
function
(
error
)
{
am
.
end
();
that
.
error
(
error
);
};
am
.
call
(
o
,
'
retreiveList
'
);
};
// end allDocs
/**
* Remove a document from several storages.
* @method remove
*/
that
.
remove
=
function
(
command
)
{
var
o
=
{},
am
=
priv
.
newAsyncModule
(),
metadata_file_path
=
command
.
getDocId
()
+
'
.metadata
'
,
current_revision
=
''
,
current_revision_file_path
=
''
,
metadata_file_content
=
null
,
on_conflict
=
false
,
conflict_object
=
{
total_rows
:
0
,
rows
:
[]
},
previous_revision
=
command
.
getOption
(
'
rev
'
)
||
'
0
'
,
previous_revision_file_path
=
command
.
getDocId
()
+
'
.
'
+
previous_revision
,
now
=
new
Date
(),
failerror
;
o
.
getDistantMetadata
=
function
()
{
priv
.
getDistantMetadata
(
command
,
metadata_file_path
,
function
(
result
)
{
metadata_file_content
=
JSON
.
parse
(
result
.
content
);
if
(
previous_revision
===
'
last
'
)
{
previous_revision
=
priv
.
chooseARevision
(
metadata_file_content
);
previous_revision_file_path
=
command
.
getDocId
()
+
'
.
'
+
previous_revision
;
}
var
previous_revision_number
=
parseInt
(
previous_revision
.
split
(
'
-
'
)[
0
],
10
)
||
0
;
// set current revision
current_revision
=
(
previous_revision_number
+
1
)
+
'
-
'
+
//jslint: removed hex_sha256(''...
hex_sha256
(
previous_revision
+
JSON
.
stringify
(
metadata_file_content
)
);
current_revision_file_path
=
command
.
getDocId
()
+
'
.
'
+
current_revision
;
am
.
call
(
o
,
'
checkForConflicts
'
);
},
function
(
error
)
{
if
(
error
.
status
===
404
)
{
am
.
call
(
o
,
'
error
'
,
[{
status
:
404
,
statusText
:
'
Not Found
'
,
error
:
'
not_found
'
,
reason
:
'
missing
'
,
message
:
'
Document not found.
'
}]);
}
else
{
am
.
call
(
o
,
'
error
'
,
[
error
]);
}
}
);
};
o
.
checkForConflicts
=
function
()
{
var
rev
;
for
(
rev
in
metadata_file_content
)
{
if
(
metadata_file_content
.
hasOwnProperty
(
rev
)
&&
rev
!==
previous_revision
)
{
on_conflict
=
true
;
failerror
=
{
status
:
409
,
error
:
'
conflict
'
,
statusText
:
'
Conflict
'
,
reason
:
'
document update conflict
'
,
message
:
'
There is one or more conflicts
'
};
break
;
}
}
am
.
call
(
o
,
'
updateMetadata
'
);
};
o
.
updateMetadata
=
function
()
{
var
previous_creation_date
,
revision_history
=
[],
id
=
''
;
if
(
metadata_file_content
[
previous_revision
])
{
previous_creation_date
=
metadata_file_content
[
previous_revision
].
_creation_date
;
revision_history
=
metadata_file_content
[
previous_revision
].
_revisions
;
delete
metadata_file_content
[
previous_revision
];
}
id
=
current_revision
;
id
=
id
.
split
(
'
-
'
);
id
.
shift
();
id
=
id
.
join
(
'
-
'
);
revision_history
.
unshift
(
id
);
metadata_file_content
[
current_revision
]
=
{
_creation_date
:
previous_creation_date
||
now
.
getTime
(),
_last_modified
:
now
.
getTime
(),
_revisions
:
revision_history
,
_conflict
:
on_conflict
,
_deleted
:
true
};
if
(
on_conflict
)
{
conflict_object
=
priv
.
createConflictObject
(
command
,
metadata_file_content
,
current_revision
);
}
am
.
call
(
o
,
'
saveMetadataOnDistant
'
);
};
o
.
saveMetadataOnDistant
=
function
()
{
priv
.
saveMetadataToDistant
(
command
,
metadata_file_path
,
metadata_file_content
,
function
()
{
am
.
call
(
o
,
'
deletePreviousRevision
'
);
if
(
on_conflict
)
{
am
.
call
(
o
,
'
error
'
);
}
else
{
am
.
call
(
o
,
'
success
'
);
}
},
function
(
error
)
{
am
.
call
(
o
,
'
error
'
,
[
error
]);
}
);
};
o
.
deletePreviousRevision
=
function
()
{
// jslint: removed /*&& !on_conflict*/
if
(
previous_revision
!==
'
0
'
)
{
priv
.
deleteAFile
(
command
,
previous_revision_file_path
,
empty_fun
,
empty_fun
);
}
};
o
.
success
=
function
(
revision
)
{
var
a
=
{
ok
:
true
,
id
:
command
.
getDocId
(),
rev
:
revision
||
current_revision
};
am
.
neverCall
(
o
,
'
error
'
);
am
.
neverCall
(
o
,
'
success
'
);
if
(
command
.
getOption
(
'
revs
'
))
{
a
.
revisions
=
priv
.
_revs
(
metadata_file_content
,
current_revision
);
}
if
(
command
.
getOption
(
'
revs_info
'
))
{
a
.
revs_info
=
priv
.
_revs_info
(
metadata_file_content
);
}
if
(
command
.
getOption
(
'
conflicts
'
))
{
a
.
conflicts
=
conflict_object
;
}
that
.
success
(
a
);
};
o
.
error
=
function
(
error
)
{
var
err
=
error
||
failerror
||
{
status
:
0
,
statusText
:
'
Unknown
'
,
error
:
'
unknown_error
'
,
message
:
'
Unknown error.
'
,
reason
:
'
unknown error
'
};
if
(
current_revision
)
{
err
.
rev
=
current_revision
;
}
if
(
command
.
getOption
(
'
revs
'
))
{
err
.
revisions
=
priv
.
_revs
(
metadata_file_content
,
current_revision
);
}
if
(
command
.
getOption
(
'
revs_info
'
))
{
err
.
revs_info
=
priv
.
_revs_info
(
metadata_file_content
);
}
if
(
command
.
getOption
(
'
conflicts
'
))
{
err
.
conflicts
=
conflict_object
;
}
am
.
neverCall
(
o
,
'
error
'
);
am
.
neverCall
(
o
,
'
success
'
);
that
.
error
(
err
);
};
am
.
call
(
o
,
'
getDistantMetadata
'
);
};
// end remove
return
that
;
});
src/jio.storage/cryptstorage.js
deleted
100644 → 0
View file @
3356688e
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global jIO: true, sjcl: true, $: true, setTimeout: true */
jIO
.
addStorageType
(
'
crypt
'
,
function
(
spec
,
my
)
{
spec
=
spec
||
{};
var
that
=
my
.
basicStorage
(
spec
,
my
),
priv
=
{},
is_valid_storage
=
(
spec
.
storage
?
true
:
false
),
super_serialized
=
that
.
serialized
;
priv
.
username
=
spec
.
username
||
''
;
priv
.
password
=
spec
.
password
||
''
;
priv
.
sub_storage_spec
=
spec
.
storage
||
{
type
:
'
base
'
};
priv
.
sub_storage_string
=
JSON
.
stringify
(
priv
.
sub_storage_string
);
that
.
serialized
=
function
()
{
var
o
=
super_serialized
();
o
.
username
=
priv
.
username
;
o
.
password
=
priv
.
password
;
// TODO : unsecured !!!
o
.
storage
=
priv
.
sub_storage_string
;
return
o
;
};
that
.
validateState
=
function
()
{
if
(
priv
.
username
&&
is_valid_storage
)
{
return
''
;
}
return
'
Need at least two parameters: "username" and "storage".
'
;
};
// TODO : IT IS NOT SECURE AT ALL!
// WE MUST REWORK CRYPTED STORAGE!
priv
.
encrypt_param_object
=
{
"
iv
"
:
"
kaprWwY/Ucr7pumXoTHbpA
"
,
"
v
"
:
1
,
"
iter
"
:
1000
,
"
ks
"
:
256
,
"
ts
"
:
128
,
"
mode
"
:
"
ccm
"
,
"
adata
"
:
""
,
"
cipher
"
:
"
aes
"
,
"
salt
"
:
"
K4bmZG9d704
"
};
priv
.
decrypt_param_object
=
{
"
iv
"
:
"
kaprWwY/Ucr7pumXoTHbpA
"
,
"
ks
"
:
256
,
"
ts
"
:
128
,
"
salt
"
:
"
K4bmZG9d704
"
};
priv
.
encrypt
=
function
(
data
,
callback
)
{
// end with a callback in order to improve encrypt to an
// asynchronous encryption.
var
tmp
=
sjcl
.
encrypt
(
priv
.
username
+
'
:
'
+
priv
.
password
,
data
,
priv
.
encrypt_param_object
);
callback
(
JSON
.
parse
(
tmp
).
ct
);
};
priv
.
decrypt
=
function
(
data
,
callback
)
{
var
tmp
,
param
=
$
.
extend
(
true
,
{},
priv
.
decrypt_param_object
);
param
.
ct
=
data
||
''
;
param
=
JSON
.
stringify
(
param
);
try
{
tmp
=
sjcl
.
decrypt
(
priv
.
username
+
'
:
'
+
priv
.
password
,
param
);
}
catch
(
e
)
{
callback
({
status
:
403
,
statusText
:
'
Forbidden
'
,
error
:
'
forbidden
'
,
message
:
'
Unable to decrypt.
'
,
reason
:
'
unable to decrypt
'
});
return
;
}
callback
(
undefined
,
tmp
);
};
priv
.
newAsyncModule
=
function
()
{
var
async
=
{};
async
.
call
=
function
(
obj
,
function_name
,
arglist
)
{
obj
.
_wait
=
obj
.
_wait
||
{};
if
(
obj
.
_wait
[
function_name
])
{
obj
.
_wait
[
function_name
]
-=
1
;
return
function
()
{};
}
// ok if undef or 0
arglist
=
arglist
||
[];
setTimeout
(
function
()
{
obj
[
function_name
].
apply
(
obj
[
function_name
],
arglist
);
});
};
async
.
neverCall
=
function
(
obj
,
function_name
)
{
obj
.
_wait
=
obj
.
_wait
||
{};
obj
.
_wait
[
function_name
]
=
-
1
;
};
async
.
wait
=
function
(
obj
,
function_name
,
times
)
{
obj
.
_wait
=
obj
.
_wait
||
{};
obj
.
_wait
[
function_name
]
=
times
;
};
async
.
end
=
function
()
{
async
.
call
=
function
()
{};
};
return
async
;
};
that
.
post
=
function
(
command
)
{
that
.
put
(
command
);
};
/**
* Saves a document.
* @method put
*/
that
.
put
=
function
(
command
)
{
var
new_file_name
,
new_file_content
,
am
=
priv
.
newAsyncModule
(),
o
=
{};
o
.
encryptFilePath
=
function
()
{
priv
.
encrypt
(
command
.
getDocId
(),
function
(
res
)
{
new_file_name
=
res
;
am
.
call
(
o
,
'
save
'
);
});
};
o
.
encryptFileContent
=
function
()
{
priv
.
encrypt
(
command
.
getDocContent
(),
function
(
res
)
{
new_file_content
=
res
;
am
.
call
(
o
,
'
save
'
);
});
};
o
.
save
=
function
()
{
var
success
=
function
(
val
)
{
val
.
id
=
command
.
getDocId
();
that
.
success
(
val
);
},
error
=
function
(
err
)
{
that
.
error
(
err
);
},
cloned_doc
=
command
.
cloneDoc
();
cloned_doc
.
_id
=
new_file_name
;
cloned_doc
.
content
=
new_file_content
;
that
.
addJob
(
'
put
'
,
priv
.
sub_storage_spec
,
cloned_doc
,
command
.
cloneOption
(),
success
,
error
);
};
am
.
wait
(
o
,
'
save
'
,
1
);
am
.
call
(
o
,
'
encryptFilePath
'
);
am
.
call
(
o
,
'
encryptFileContent
'
);
};
// end put
/**
* Loads a document.
* @method get
*/
that
.
get
=
function
(
command
)
{
var
new_file_name
,
am
=
priv
.
newAsyncModule
(),
o
=
{};
o
.
encryptFilePath
=
function
()
{
priv
.
encrypt
(
command
.
getDocId
(),
function
(
res
)
{
new_file_name
=
res
;
am
.
call
(
o
,
'
get
'
);
});
};
o
.
get
=
function
()
{
that
.
addJob
(
'
get
'
,
priv
.
sub_storage_spec
,
new_file_name
,
command
.
cloneOption
(),
o
.
success
,
o
.
error
);
};
o
.
success
=
function
(
val
)
{
val
.
_id
=
command
.
getDocId
();
if
(
command
.
getOption
(
'
metadata_only
'
))
{
that
.
success
(
val
);
}
else
{
priv
.
decrypt
(
val
.
content
,
function
(
err
,
res
)
{
if
(
err
)
{
that
.
error
(
err
);
}
else
{
val
.
content
=
res
;
that
.
success
(
val
);
}
});
}
};
o
.
error
=
function
(
error
)
{
that
.
error
(
error
);
};
am
.
call
(
o
,
'
encryptFilePath
'
);
};
// end get
/**
* Gets a document list.
* @method allDocs
*/
that
.
allDocs
=
function
(
command
)
{
var
result_array
=
[],
am
=
priv
.
newAsyncModule
(),
o
=
{};
o
.
allDocs
=
function
()
{
that
.
addJob
(
'
allDocs
'
,
priv
.
sub_storage_spec
,
null
,
command
.
cloneOption
(),
o
.
onSuccess
,
o
.
error
);
};
o
.
onSuccess
=
function
(
val
)
{
if
(
val
.
total_rows
===
0
)
{
return
am
.
call
(
o
,
'
success
'
);
}
result_array
=
val
.
rows
;
var
i
,
decrypt
=
function
(
c
)
{
priv
.
decrypt
(
result_array
[
c
].
id
,
function
(
err
,
res
)
{
if
(
err
)
{
am
.
call
(
o
,
'
error
'
,
[
err
]);
}
else
{
result_array
[
c
].
id
=
res
;
result_array
[
c
].
key
=
res
;
am
.
call
(
o
,
'
success
'
);
}
});
if
(
!
command
.
getOption
(
'
metadata_only
'
))
{
priv
.
decrypt
(
result_array
[
c
].
value
.
content
,
function
(
err
,
res
)
{
if
(
err
)
{
am
.
call
(
o
,
'
error
'
,
[
err
]);
}
else
{
result_array
[
c
].
value
.
content
=
res
;
am
.
call
(
o
,
'
success
'
);
}
}
);
}
};
if
(
command
.
getOption
(
'
metadata_only
'
))
{
am
.
wait
(
o
,
'
success
'
,
val
.
total_rows
-
1
);
}
else
{
am
.
wait
(
o
,
'
success
'
,
val
.
total_rows
*
2
-
1
);
}
for
(
i
=
0
;
i
<
result_array
.
length
;
i
+=
1
)
{
decrypt
(
i
);
}
};
o
.
error
=
function
(
error
)
{
am
.
end
();
that
.
error
(
error
);
};
o
.
success
=
function
()
{
am
.
end
();
that
.
success
({
total_rows
:
result_array
.
length
,
rows
:
result_array
});
};
am
.
call
(
o
,
'
allDocs
'
);
};
// end allDocs
/**
* Removes a document.
* @method remove
*/
that
.
remove
=
function
(
command
)
{
var
new_file_name
,
o
=
{};
o
.
encryptDocId
=
function
()
{
priv
.
encrypt
(
command
.
getDocId
(),
function
(
res
)
{
new_file_name
=
res
;
o
.
removeDocument
();
});
};
o
.
removeDocument
=
function
()
{
var
cloned_doc
=
command
.
cloneDoc
();
cloned_doc
.
_id
=
new_file_name
;
that
.
addJob
(
'
remove
'
,
priv
.
sub_storage_spec
,
cloned_doc
,
command
.
cloneOption
(),
o
.
success
,
that
.
error
);
};
o
.
success
=
function
(
val
)
{
val
.
id
=
command
.
getDocId
();
that
.
success
(
val
);
};
o
.
encryptDocId
();
};
// end remove
return
that
;
});
src/jio.storage/davstorage.js
deleted
100644 → 0
View file @
3356688e
/*
* Copyright 2013, Nexedi SA
* Released under the LGPL license.
* http://www.gnu.org/licenses/lgpl.html
*/
/*jslint indent: 2, maxlen: 80, nomen: true */
/*global define, jIO, jQuery, btoa */
// JIO Dav Storage Description :
// {
// type: "dav",
// url: {string}
// }
// {
// type: "dav",
// url: {string},
// auth_type: {string}, (optional)
// - "auto" (default) (not implemented)
// - "basic"
// - "digest" (not implemented)
// realm: {string}, (optional)
// - undefined (default) (not implemented)
// - "<string>" realm name (not implemented)
// username: {string},
// password: {string} (optional)
// }
// {
// type: "dav",
// url: {string},
// encoded_login: {string}
// }
// {
// type: "dav",
// url: {string},
// secured_login: {string} (not implemented)
// }
// NOTE: to get the authentication type ->
// curl --verbose -X OPTION http://domain/
// In the headers: "WWW-Authenticate: Basic realm="DAV-upload"
// URL Characters convertion:
// If I want to retrieve the file which id is -> http://100%.json
// http://domain/collection/http://100%.json cannot be applied
// - '/' is col separator,
// - '?' is url/parameter separator
// - '%' is special char
// - '.' document and attachment separator
// http://100%.json will become
// - http:%2F%2F100%25.json to avoid bad request ('/', '%' -> '%2F', '%25')
// - http:%2F%2F100%25_.json to avoid ids conflicts ('.' -> '_.')
// - http:%252F%252F100%2525_.json to avoid request bad interpretation
// ('%', '%25')
// The file will be saved as http:%2F%2F100%25_.json
// define([module_name], [dependencies], module);
(
function
(
dependencies
,
module
)
{
"
use strict
"
;
if
(
typeof
define
===
'
function
'
&&
define
.
amd
)
{
return
define
(
dependencies
,
module
);
}
module
(
jIO
,
jQuery
);
}([
'
jio
'
,
'
jquery
'
],
function
(
jIO
,
$
)
{
"
use strict
"
;
jIO
.
addStorageType
(
"
dav
"
,
function
(
spec
,
my
)
{
var
priv
=
{},
that
=
my
.
basicStorage
(
spec
,
my
),
dav
=
{};
// ATTRIBUTES //
priv
.
url
=
null
;
priv
.
username
=
null
;
priv
.
password
=
null
;
priv
.
encoded_login
=
null
;
// CONSTRUCTOR //
/**
* Init the dav storage connector thanks to the description
* @method __init__
* @param {object} description The description object
*/
priv
.
__init__
=
function
(
description
)
{
priv
.
url
=
description
.
url
||
""
;
priv
.
url
=
priv
.
removeSlashIfLast
(
priv
.
url
);
// if (description.secured_login) {
// not implemented
// } else
if
(
description
.
encoded_login
)
{
priv
.
encoded_login
=
description
.
encoded_login
;
}
else
if
(
description
.
auth_type
)
{
if
(
description
.
auth_type
===
"
basic
"
)
{
priv
.
encoded_login
=
"
Basic
"
+
btoa
((
description
.
username
||
""
)
+
"
:
"
+
(
description
.
password
||
""
));
}
}
else
{
priv
.
encoded_login
=
""
;
}
};
// OVERRIDES //
that
.
specToStore
=
function
()
{
// TODO: secured password
// The encoded_login can be seen by anyone,
// we must find a way to secure it!
// secured_login = encrypt(encoded_login)
// encoded_login = decrypt(secured_login)
return
{
"
url
"
:
priv
.
url
,
"
encoded_login
"
:
priv
.
encoded_login
};
};
that
.
validateState
=
function
()
{
if
(
typeof
priv
.
url
!==
"
string
"
||
priv
.
url
===
""
)
{
return
"
The webDav server URL is not provided
"
;
}
if
(
priv
.
encoded_login
===
null
)
{
return
"
Impossible to create the authorization
"
;
}
return
""
;
};
// TOOLS //
/**
* Generate a new uuid
* @method generateUuid
* @return {string} The new uuid
*/
priv
.
generateUuid
=
function
()
{
var
S4
=
function
()
{
/* 65536 */
var
i
,
string
=
Math
.
floor
(
Math
.
random
()
*
0x10000
).
toString
(
16
);
for
(
i
=
string
.
length
;
i
<
4
;
i
+=
1
)
{
string
=
"
0
"
+
string
;
}
return
string
;
};
return
S4
()
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
S4
()
+
S4
();
};
// /**
// * Clones an object in deep
// * @method clone
// * @param {object} object The object to clone
// * @return {object} The cloned object
// */
// priv.clone = function (object) {
// var tmp = JSON.stringify(object);
// if (tmp === undefined) {
// return undefined;
// }
// return JSON.parse(tmp);
// };
/**
* Replace substrings to another strings
* @method recursiveReplace
* @param {string} string The string to do replacement
* @param {array} list_of_replacement An array of couple
* ["substring to select", "selected substring replaced by this string"].
* @return {string} The replaced string
*/
priv
.
recursiveReplace
=
function
(
string
,
list_of_replacement
)
{
var
i
,
split_string
=
string
.
split
(
list_of_replacement
[
0
][
0
]);
if
(
list_of_replacement
[
1
])
{
for
(
i
=
0
;
i
<
split_string
.
length
;
i
+=
1
)
{
split_string
[
i
]
=
priv
.
recursiveReplace
(
split_string
[
i
],
list_of_replacement
.
slice
(
1
)
);
}
}
return
split_string
.
join
(
list_of_replacement
[
0
][
1
]);
};
/**
* Changes spaces to %20, / to %2f, % to %25 and ? to %3f
* @method secureName
* @param {string} name The name to secure
* @return {string} The secured name
*/
priv
.
secureName
=
function
(
name
)
{
return
priv
.
recursiveReplace
(
name
,
[
[
"
"
,
"
%20
"
],
[
"
/
"
,
"
%2F
"
],
[
"
%
"
,
"
%25
"
],
[
"
?
"
,
"
%3F
"
]
]);
};
/**
* Restores the original name from a secured name
* @method restoreName
* @param {string} secured_name The secured name to restore
* @return {string} The original name
*/
priv
.
restoreName
=
function
(
secured_name
)
{
return
priv
.
recursiveReplace
(
secured_name
,
[
[
"
%20
"
,
"
"
],
[
"
%2F
"
,
"
/
"
],
[
"
%25
"
,
"
%
"
],
[
"
%3F
"
,
"
?
"
]
]);
};
/**
* Convert document id and attachment id to a file name
* @method idsToFileName
* @param {string} doc_id The document id
* @param {string} attachment_id The attachment id (optional)
* @return {string} The file name
*/
priv
.
idsToFileName
=
function
(
doc_id
,
attachment_id
)
{
doc_id
=
priv
.
secureName
(
doc_id
).
split
(
"
.
"
).
join
(
"
_.
"
);
if
(
typeof
attachment_id
===
"
string
"
)
{
attachment_id
=
priv
.
secureName
(
attachment_id
).
split
(
"
.
"
).
join
(
"
_.
"
);
return
doc_id
+
"
.
"
+
attachment_id
;
}
return
doc_id
;
};
/**
* Convert a file name to a document id (and attachment id if there)
* @method fileNameToIds
* @param {string} file_name The file name to convert
* @return {array} ["document id", "attachment id"] or ["document id"]
*/
priv
.
fileNameToIds
=
function
(
file_name
)
{
var
separator_index
=
-
1
,
split
=
file_name
.
split
(
"
.
"
);
split
.
slice
(
0
,
-
1
).
forEach
(
function
(
file_name_part
,
index
)
{
if
(
file_name_part
.
slice
(
-
1
)
!==
"
_
"
)
{
if
(
separator_index
!==
-
1
)
{
separator_index
=
new
TypeError
(
"
Corrupted file name
"
);
separator_index
.
status
=
24
;
throw
separator_index
;
}
separator_index
=
index
;
}
});
if
(
separator_index
===
-
1
)
{
return
[
priv
.
restoreName
(
priv
.
restoreName
(
file_name
).
split
(
"
_.
"
).
join
(
"
.
"
))];
}
return
[
priv
.
restoreName
(
priv
.
restoreName
(
split
.
slice
(
0
,
separator_index
+
1
).
join
(
"
.
"
)
).
split
(
"
_.
"
).
join
(
"
.
"
)),
priv
.
restoreName
(
priv
.
restoreName
(
split
.
slice
(
separator_index
+
1
).
join
(
"
.
"
)
).
split
(
"
_.
"
).
join
(
"
.
"
))
];
};
/**
* Removes the last character if it is a "/". "/a/b/c/" become "/a/b/c"
* @method removeSlashIfLast
* @param {string} string The string to modify
* @return {string} The modified string
*/
priv
.
removeSlashIfLast
=
function
(
string
)
{
if
(
string
[
string
.
length
-
1
]
===
"
/
"
)
{
return
string
.
slice
(
0
,
-
1
);
}
return
string
;
};
/**
* Modify an ajax object to add default values
* @method makeAjaxObject
* @param {string} file_name The file name to add to the url
* @param {object} ajax_object The ajax object to override
* @return {object} A new ajax object with default values
*/
priv
.
makeAjaxObject
=
function
(
file_name
,
method
,
ajax_object
)
{
ajax_object
.
type
=
method
||
ajax_object
.
type
||
"
GET
"
;
ajax_object
.
url
=
priv
.
url
+
"
/
"
+
priv
.
secureName
(
file_name
)
+
"
?_=
"
+
Date
.
now
();
ajax_object
.
async
=
ajax_object
.
async
===
false
?
false
:
true
;
ajax_object
.
crossdomain
=
ajax_object
.
crossdomain
===
false
?
false
:
true
;
ajax_object
.
headers
=
ajax_object
.
headers
||
{};
ajax_object
.
headers
.
Authorization
=
ajax_object
.
headers
.
Authorization
||
priv
.
encoded_login
;
return
ajax_object
;
};
/**
* Runs all ajax requests for davStorage
* @method ajax
* @param {string} doc_id The document id
* @param {string} attachment_id The attachment id, can be undefined
* @param {string} method The request method
* @param {object} ajax_object The request parameters (optional)
*/
priv
.
ajax
=
function
(
doc_id
,
attachment_id
,
method
,
ajax_object
)
{
var
new_ajax_object
=
JSON
.
parse
(
JSON
.
stringify
(
ajax_object
)
||
"
{}
"
);
return
$
.
ajax
(
priv
.
makeAjaxObject
(
priv
.
idsToFileName
(
doc_id
||
''
,
attachment_id
),
method
,
new_ajax_object
));
//.always(then || function () {});
};
/**
* Creates error objects for this storage
* @method createError
* @param {string} url url to clean up
* @return {object} error The error object
*/
priv
.
createError
=
function
(
status
,
message
,
reason
)
{
var
error
=
{
"
status
"
:
status
,
"
message
"
:
message
,
"
reason
"
:
reason
};
switch
(
status
)
{
case
404
:
error
.
statusText
=
"
Not found
"
;
break
;
case
405
:
error
.
statusText
=
"
Method Not Allowed
"
;
break
;
case
409
:
error
.
statusText
=
"
Conflicts
"
;
break
;
case
24
:
error
.
statusText
=
"
Corrupted Document
"
;
break
;
}
error
.
error
=
error
.
statusText
.
toLowerCase
().
split
(
"
"
).
join
(
"
_
"
);
return
error
;
};
/**
* Converts ajax error object to a JIO error object
* @method ajaxErrorToJioError
* @param {object} ajax_error_object The ajax error object
* @param {string} message The error message
* @param {string} reason The error reason
* @return {object} The JIO error object
*/
priv
.
ajaxErrorToJioError
=
function
(
ajax_error_object
,
message
,
reason
)
{
var
jio_error_object
=
{};
jio_error_object
.
status
=
ajax_error_object
.
status
;
jio_error_object
.
statusText
=
ajax_error_object
.
statusText
;
jio_error_object
.
error
=
ajax_error_object
.
statusText
.
toLowerCase
().
split
(
"
"
).
join
(
"
_
"
);
jio_error_object
.
message
=
message
;
jio_error_object
.
reason
=
reason
;
return
jio_error_object
;
};
/**
* Function that create an object containing jQuery like callbacks
* @method makeJQLikeCallback
* @return {object} jQuery like callback methods
*/
priv
.
makeJQLikeCallback
=
function
()
{
var
result
=
null
,
emptyFun
=
function
()
{},
jql
=
{
"
respond
"
:
function
()
{
result
=
arguments
;
},
"
to_return
"
:
{
"
always
"
:
function
(
func
)
{
if
(
result
)
{
func
.
apply
(
func
,
result
);
jql
.
to_return
.
always
=
emptyFun
;
}
else
{
jql
.
respond
=
func
;
}
return
jql
.
to_return
;
},
"
then
"
:
function
(
func
)
{
if
(
result
)
{
func
(
result
[
1
]);
jql
.
to_return
.
then
=
emptyFun
;
}
else
{
jql
.
respond
=
function
(
err
,
response
)
{
func
(
response
);
};
}
return
jql
.
to_return
;
}
}
};
return
jql
;
};
// DAV REQUESTS //
/**
* Retrieve a document file
* @method dav.getDocument
* @param {string} doc_id The document id
*/
dav
.
getDocument
=
function
(
doc_id
)
{
var
doc
,
jql
=
priv
.
makeJQLikeCallback
(),
error
=
null
;
priv
.
ajax
(
doc_id
,
undefined
,
"
GET
"
).
always
(
function
(
one
,
state
,
three
)
{
if
(
state
!==
"
success
"
)
{
error
=
priv
.
ajaxErrorToJioError
(
one
,
"
Cannot retrieve document
"
,
"
Unknown
"
);
if
(
one
.
status
===
404
)
{
error
.
reason
=
"
Not Found
"
;
}
return
jql
.
respond
(
error
,
undefined
);
}
try
{
doc
=
JSON
.
parse
(
one
);
}
catch
(
e
)
{
return
jql
.
respond
(
priv
.
createError
(
24
,
"
Cannot parse document
"
,
"
Document is corrupted
"
),
undefined
);
}
// document health is good
return
jql
.
respond
(
undefined
,
doc
);
});
return
jql
.
to_return
;
};
/**
* Retrieve an attachment file
* @method dav.getAttachment
* @param {string} doc_id The document id
* @param {string} attachment_id The attachment id
*/
dav
.
getAttachment
=
function
(
doc_id
,
attachment_id
)
{
var
jql
=
priv
.
makeJQLikeCallback
(),
error
=
null
;
priv
.
ajax
(
doc_id
,
attachment_id
,
"
GET
"
).
always
(
function
(
one
,
state
,
three
)
{
if
(
state
!==
"
success
"
)
{
error
=
priv
.
ajaxErrorToJioError
(
one
,
"
Cannot retrieve attachment
"
,
"
Unknown
"
);
if
(
one
.
status
===
404
)
{
error
.
reason
=
"
Not Found
"
;
}
return
jql
.
respond
(
error
,
undefined
);
}
return
jql
.
respond
(
undefined
,
one
);
});
return
jql
.
to_return
;
};
/**
* Uploads a document file
* @method dav.putDocument
* @param {object} doc The document object
*/
dav
.
putDocument
=
function
(
doc
)
{
var
jql
=
priv
.
makeJQLikeCallback
();
priv
.
ajax
(
doc
.
_id
,
undefined
,
"
PUT
"
,
{
"
dataType
"
:
"
text
"
,
"
data
"
:
JSON
.
stringify
(
doc
)
}).
always
(
function
(
one
,
state
,
three
)
{
if
(
state
!==
"
success
"
)
{
return
jql
.
respond
(
priv
.
ajaxErrorToJioError
(
one
,
"
Cannot upload document
"
,
"
Unknown
"
),
undefined
);
}
jql
.
respond
(
undefined
,
{
"
ok
"
:
true
,
"
id
"
:
doc
.
_id
});
});
return
jql
.
to_return
;
};
/**
* Uploads an attachment file
* @method dav.putAttachment
* @param {string} doc_id The document id
* @param {string} attachment_id The attachment id
* @param {string} data The attachment data
*/
dav
.
putAttachment
=
function
(
doc_id
,
attachment_id
,
data
)
{
var
jql
=
priv
.
makeJQLikeCallback
();
priv
.
ajax
(
doc_id
,
attachment_id
,
"
PUT
"
,
{
"
dataType
"
:
"
text
"
,
"
data
"
:
data
}).
always
(
function
(
one
,
state
,
three
)
{
if
(
state
!==
"
success
"
)
{
return
jql
.
respond
(
priv
.
ajaxErrorToJioError
(
one
,
"
Cannot upload attachment
"
,
"
Unknown
"
),
undefined
);
}
return
jql
.
respond
(
undefined
,
{
"
ok
"
:
true
,
"
id
"
:
doc_id
,
"
attachment
"
:
attachment_id
});
});
return
jql
.
to_return
;
};
/**
* Deletes a document file
* @method dav.removeDocument
* @param {string} doc_id The document id
*/
dav
.
removeDocument
=
function
(
doc_id
)
{
var
jql
=
priv
.
makeJQLikeCallback
(),
error
=
null
;
priv
.
ajax
(
doc_id
,
undefined
,
"
DELETE
"
).
always
(
function
(
one
,
state
,
three
)
{
if
(
state
!==
"
success
"
)
{
error
=
priv
.
ajaxErrorToJioError
(
one
,
"
Cannot delete document
"
,
"
Unknown
"
);
if
(
one
.
status
===
404
)
{
error
.
reason
=
"
Not Found
"
;
}
return
jql
.
respond
(
error
,
undefined
);
}
jql
.
respond
(
undefined
,
{
"
ok
"
:
true
,
"
id
"
:
doc_id
});
});
return
jql
.
to_return
;
};
/**
* Deletes an attachment file
* @method dav.removeAttachment
* @param {string} doc_id The document id
* @param {string} attachment_id The attachment id
*/
dav
.
removeAttachment
=
function
(
doc_id
,
attachment_id
)
{
var
jql
=
priv
.
makeJQLikeCallback
(),
error
=
null
;
priv
.
ajax
(
doc_id
,
attachment_id
,
"
DELETE
"
).
always
(
function
(
one
,
state
,
three
)
{
if
(
state
!==
"
success
"
)
{
error
=
priv
.
ajaxErrorToJioError
(
one
,
"
Cannot delete attachment
"
,
"
Unknown
"
);
if
(
one
.
status
===
404
)
{
error
.
reason
=
"
Not Found
"
;
}
return
jql
.
respond
(
error
,
undefined
);
}
jql
.
respond
(
undefined
,
{
"
ok
"
:
true
,
"
id
"
:
doc_id
});
});
return
jql
.
to_return
;
};
/**
* Get a list of document file
* @method dav.allDocs
*/
dav
.
allDocs
=
function
()
{
var
jql
=
priv
.
makeJQLikeCallback
(),
rows
=
[];
priv
.
ajax
(
undefined
,
undefined
,
"
PROPFIND
"
,
{
"
dataType
"
:
"
xml
"
,
"
headers
"
:
{
"
Depth
"
:
1
}
}).
always
(
function
(
one
,
state
,
three
)
{
var
response
,
len
;
if
(
state
!==
"
success
"
)
{
return
jql
.
respond
(
priv
.
ajaxErrorToJioError
(
one
,
"
Cannot get the document list
"
,
"
Unknown
"
),
undefined
);
}
response
=
$
(
one
).
find
(
"
D
\\
:response, response
"
);
len
=
response
.
length
;
if
(
len
===
1
)
{
return
jql
.
respond
({
"
total_rows
"
:
0
,
"
rows
"
:
[]});
}
response
.
each
(
function
(
i
,
data
)
{
var
row
;
if
(
i
>
0
)
{
// exclude parent folder
row
=
{
"
id
"
:
""
,
"
key
"
:
""
,
"
value
"
:
{}
};
$
(
data
).
find
(
"
D
\\
:href, href
"
).
each
(
function
()
{
row
.
id
=
$
(
this
).
text
().
split
(
'
/
'
).
slice
(
-
1
)[
0
];
try
{
row
.
id
=
priv
.
fileNameToIds
(
row
.
id
);
}
catch
(
e
)
{
if
(
e
.
name
===
"
TypeError
"
&&
e
.
status
===
24
)
{
return
;
}
throw
e
;
}
if
(
row
.
id
.
length
!==
1
)
{
row
=
undefined
;
}
else
{
row
.
id
=
row
.
id
[
0
];
row
.
key
=
row
.
id
;
}
});
if
(
row
!==
undefined
)
{
rows
.
push
(
row
);
}
}
});
jql
.
respond
(
undefined
,
{
"
total_rows
"
:
rows
.
length
,
"
rows
"
:
rows
});
});
return
jql
.
to_return
;
};
// JIO COMMANDS //
// wedDav methods rfc4918 (short summary)
// COPY Reproduces single resources (files) and collections (directory
// trees). Will overwrite files (if specified by request) but will
// respond 209 (Conflict) if it would overwrite a tree
// DELETE deletes files and directory trees
// GET just the vanilla HTTP/1.1 behaviour
// HEAD ditto
// LOCK locks a resources
// MKCOL creates a directory
// MOVE Moves (rename or copy) a file or a directory tree. Will
// 'overwrite' files (if specified by the request) but will respond
// 209 (Conflict) if it would overwrite a tree.
// OPTIONS If WebDAV is enabled and available for the path this reports the
// WebDAV extension methods
// PROPFIND Retrieves the requested file characteristics, DAV lock status
// and 'dead' properties for individual files, a directory and its
// child files, or a directory tree
// PROPPATCHset and remove 'dead' meta-data properties
// PUT Update or create resource or collections
// UNLOCK unlocks a resource
// Notes: all Ajax requests should be CORS (cross-domain)
// adding custom headers triggers preflight OPTIONS request!
// http://remysharp.com/2011/04/21/getting-cors-working/
/**
* Creates a new document
* @method post
* @param {object} command The JIO command
*/
that
.
post
=
function
(
command
)
{
var
doc_id
=
command
.
getDocId
()
||
priv
.
generateUuid
();
dav
.
getDocument
(
doc_id
).
always
(
function
(
err
,
response
)
{
if
(
err
)
{
if
(
err
.
status
===
404
)
{
// the document does not already exist
// updating document
var
doc
=
command
.
cloneDoc
();
doc
.
_id
=
doc_id
;
return
dav
.
putDocument
(
doc
).
always
(
function
(
err
,
response
)
{
if
(
err
)
{
return
that
.
retry
(
err
);
}
return
that
.
success
(
response
);
});
}
if
(
err
.
status
===
24
)
{
return
that
.
error
(
err
);
}
// an error occured
return
that
.
retry
(
err
);
}
// the document already exists
return
that
.
error
(
priv
.
createError
(
405
,
"
Cannot create document
"
,
"
Document already exists
"
));
});
};
/**
* Creates or updates a document
* @method put
* @param {object} command The JIO command
*/
that
.
put
=
function
(
command
)
{
dav
.
putDocument
(
command
.
cloneDoc
()).
always
(
function
(
err
,
response
)
{
if
(
err
)
{
// an error occured
return
that
.
retry
(
err
);
}
// document updated
return
that
.
success
(
response
);
});
};
/**
* Add an attachment to a document
* @method putAttachment
* @param {object} command The JIO command
*/
that
.
putAttachment
=
function
(
command
)
{
var
doc
=
null
,
doc_id
=
command
.
getDocId
(),
attachment_id
,
tmp
;
attachment_id
=
command
.
getAttachmentId
();
dav
.
getDocument
(
doc_id
).
always
(
function
(
err
,
response
)
{
if
(
err
)
{
// document not found or error
tmp
=
that
.
retry
;
if
(
err
.
status
===
404
||
err
.
status
===
24
)
{
tmp
=
that
.
error
;
}
return
tmp
(
err
);
}
doc
=
response
;
doc
.
_attachments
=
doc
.
_attachments
||
{};
doc
.
_attachments
[
attachment_id
]
=
{
"
length
"
:
command
.
getAttachmentLength
(),
"
digest
"
:
"
md5-
"
+
command
.
md5SumAttachmentData
(),
"
content_type
"
:
command
.
getAttachmentMimeType
()
};
// put the attachment
dav
.
putAttachment
(
doc_id
,
attachment_id
,
command
.
getAttachmentData
()
).
always
(
function
(
err
,
response
)
{
if
(
err
)
{
// an error occured
return
that
.
retry
(
err
);
}
// update the document
dav
.
putDocument
(
doc
).
always
(
function
(
err
,
response
)
{
if
(
err
)
{
return
that
.
retry
(
err
);
}
response
.
attachment
=
attachment_id
;
return
that
.
success
(
response
);
});
});
});
};
/**
* Get a document
* @method get
* @param {object} command The JIO command
*/
that
.
get
=
function
(
command
)
{
dav
.
getDocument
(
command
.
getDocId
()).
always
(
function
(
err
,
response
)
{
if
(
err
)
{
if
(
err
.
status
===
404
||
err
.
status
===
24
)
{
return
that
.
error
(
err
);
}
return
that
.
retry
(
err
);
}
return
that
.
success
(
response
);
});
};
/**
* Get an attachment
* @method getAttachment
* @param {object} command The JIO command
*/
that
.
getAttachment
=
function
(
command
)
{
dav
.
getAttachment
(
command
.
getDocId
(),
command
.
getAttachmentId
()
).
always
(
function
(
err
,
response
)
{
if
(
err
)
{
if
(
err
.
status
===
404
)
{
return
that
.
error
(
err
);
}
return
that
.
retry
(
err
);
}
return
that
.
success
(
response
);
});
};
/**
* Remove a document
* @method remove
* @param {object} command The JIO command
*/
that
.
remove
=
function
(
command
)
{
var
doc_id
=
command
.
getDocId
(),
count
=
0
,
end
;
end
=
function
()
{
count
-=
1
;
if
(
count
===
0
)
{
that
.
success
({
"
ok
"
:
true
,
"
id
"
:
doc_id
});
}
};
dav
.
getDocument
(
doc_id
).
always
(
function
(
err
,
response
)
{
var
attachment_id
=
null
;
if
(
err
)
{
if
(
err
.
status
===
404
)
{
return
that
.
error
(
err
);
}
if
(
err
.
status
!==
24
)
{
// 24 -> corrupted document
return
that
.
retry
(
err
);
}
response
=
{};
}
count
+=
2
;
dav
.
removeDocument
(
doc_id
).
always
(
function
(
err
,
response
)
{
if
(
err
)
{
if
(
err
.
status
===
404
)
{
return
that
.
error
(
err
);
}
return
that
.
retry
(
err
);
}
return
end
();
});
for
(
attachment_id
in
response
.
_attachments
)
{
if
(
response
.
_attachments
.
hasOwnProperty
(
attachment_id
))
{
count
+=
1
;
dav
.
removeAttachment
(
doc_id
,
attachment_id
).
always
(
end
);
}
}
end
();
});
};
/**
* Remove an attachment
* @method removeAttachment
* @param {object} command The JIO command
*/
that
.
removeAttachment
=
function
(
command
)
{
var
doc_id
=
command
.
getDocId
(),
doc
,
attachment_id
;
attachment_id
=
command
.
getAttachmentId
();
dav
.
getDocument
(
doc_id
).
always
(
function
(
err
,
response
)
{
var
still_has_attachments
;
if
(
err
)
{
if
(
err
.
status
===
404
||
err
.
status
===
24
)
{
return
that
.
error
(
err
);
}
return
that
.
retry
(
err
);
}
doc
=
response
;
if
(
typeof
(
doc
.
_attachments
||
{})[
attachment_id
]
!==
"
object
"
)
{
return
that
.
error
(
priv
.
createError
(
404
,
"
Cannot remove attachment
"
,
"
Not Found
"
));
}
delete
doc
.
_attachments
[
attachment_id
];
// check if there is still attachments
for
(
still_has_attachments
in
doc
.
_attachments
)
{
if
(
doc
.
_attachments
.
hasOwnProperty
(
still_has_attachments
))
{
break
;
}
}
if
(
still_has_attachments
===
undefined
)
{
delete
doc
.
_attachments
;
}
doc
.
_id
=
doc_id
;
dav
.
putDocument
(
doc
).
always
(
function
(
err
,
response
)
{
if
(
err
)
{
return
that
.
retry
(
err
);
}
dav
.
removeAttachment
(
doc_id
,
attachment_id
).
always
(
function
(
err
,
response
)
{
that
.
success
({
"
ok
"
:
true
,
"
id
"
:
doc_id
,
"
attachment
"
:
attachment_id
});
});
});
});
};
/**
* Gets a document list from a distant dav storage
* Options:
* - {boolean} include_docs Also retrieve the actual document content.
* @method allDocs
* @param {object} command The JIO command
*/
that
.
allDocs
=
function
(
command
)
{
var
count
=
0
,
end
,
rows
;
end
=
function
()
{
count
-=
1
;
if
(
count
===
0
)
{
that
.
success
(
rows
);
}
};
dav
.
allDocs
().
always
(
function
(
err
,
response
)
{
if
(
err
)
{
return
that
.
retry
(
err
);
}
if
(
command
.
getOption
(
"
include_docs
"
)
===
true
)
{
count
+=
1
;
rows
=
response
;
rows
.
rows
.
forEach
(
function
(
row
)
{
count
+=
1
;
dav
.
getDocument
(
row
.
id
).
always
(
function
(
err
,
response
)
{
if
(
err
)
{
if
(
err
.
status
===
404
||
err
.
status
===
24
)
{
return
that
.
error
(
err
);
}
return
that
.
retry
(
err
);
}
row
.
doc
=
response
;
end
();
});
});
end
();
}
else
{
that
.
success
(
response
);
}
});
};
priv
.
__init__
(
spec
);
return
that
;
});
}));
src/jio.storage/erp5storage.js
deleted
100644 → 0
View file @
3356688e
/*
* Copyright 2013, Nexedi SA
* Released under the LGPL license.
* http://www.gnu.org/licenses/lgpl.html
*/
/*jslint indent: 2, maxlen: 80, nomen: true */
/*global jIO: true, $: true, complex_queries: true */
// JIO Erp5 Storage Description :
// {
// type: "erp5"
// url: {string}
// mode: {string} (optional)
// - "generic" (default)
// - "erp5_only"
//
// with
//
// auth_type: {string} (optional)
// - "none" (default)
// - "basic" (not implemented)
// - "digest" (not implemented)
// username: {string}
// password: {string} (optional)
// - no password (default)
//
// or
//
// encoded_login: {string} (not implemented)
//
// or
//
// secured_login: {string} (not implemented)
// }
jIO
.
addStorageType
(
"
erp5
"
,
function
(
spec
,
my
)
{
"
use strict
"
;
var
priv
=
{},
that
=
my
.
basicStorage
(
spec
,
my
),
erp5
=
{};
// ATTRIBUTES //
priv
.
url
=
null
;
priv
.
mode
=
"
generic
"
;
priv
.
auth_type
=
"
none
"
;
priv
.
encoded_login
=
null
;
// CONSTRUCTOR //
/**
* Init the erp5 storage connector thanks to the description
* @method __init__
* @param {object} description The description object
*/
priv
.
__init__
=
function
(
description
)
{
priv
.
url
=
description
.
url
||
""
;
priv
.
url
=
priv
.
removeSlashIfLast
(
priv
.
url
);
if
(
description
.
mode
===
"
erp5_only
"
)
{
priv
.
mode
=
"
erp5_only
"
;
}
if
(
description
.
encoded_login
)
{
priv
.
encoded_login
=
description
.
encoded_login
;
}
else
{
if
(
description
.
username
)
{
priv
.
encoded_login
=
"
__ac_name=
"
+
priv
.
convertToUrlParameter
(
description
.
username
)
+
"
&
"
+
(
typeof
description
.
password
===
"
string
"
?
"
__ac_password=
"
+
priv
.
convertToUrlParameter
(
description
.
password
)
+
"
&
"
:
""
);
}
else
{
priv
.
encoded_login
=
""
;
}
}
};
// OVERRIDES //
that
.
specToStore
=
function
()
{
// TODO: secured password
// The encoded_login can be seen by anyone, we must find a way to secure it!
// secured_login = encrypt(encoded_login)
// encoded_login = decrypt(secured_login)
return
{
"
url
"
:
priv
.
url
,
"
mode
"
:
priv
.
mode
,
"
encoded_login
"
:
priv
.
encoded_login
};
};
that
.
validateState
=
function
()
{
if
(
typeof
priv
.
url
!==
"
string
"
||
priv
.
url
===
""
)
{
return
"
The erp5 server URL is not provided
"
;
}
if
(
priv
.
encoded_login
===
null
)
{
return
"
Impossible to create the authorization
"
;
}
return
""
;
};
// TOOLS //
/**
* Replace substrings to another strings
* @method recursiveReplace
* @param {string} string The string to do replacement
* @param {array} list_of_replacement An array of couple
* ["substring to select", "selected substring replaced by this string"].
* @return {string} The replaced string
*/
priv
.
recursiveReplace
=
function
(
string
,
list_of_replacement
)
{
var
i
,
split_string
=
string
.
split
(
list_of_replacement
[
0
][
0
]);
if
(
list_of_replacement
[
1
])
{
for
(
i
=
0
;
i
<
split_string
.
length
;
i
+=
1
)
{
split_string
[
i
]
=
priv
.
recursiveReplace
(
split_string
[
i
],
list_of_replacement
.
slice
(
1
)
);
}
}
return
split_string
.
join
(
list_of_replacement
[
0
][
1
]);
};
/**
* Changes & to %26
* @method convertToUrlParameter
* @param {string} parameter The parameter to convert
* @return {string} The converted parameter
*/
priv
.
convertToUrlParameter
=
function
(
parameter
)
{
return
priv
.
recursiveReplace
(
parameter
,
[[
"
"
,
"
%20
"
],
[
"
&
"
,
"
%26
"
]]);
};
/**
* Removes the last character if it is a "/". "/a/b/c/" become "/a/b/c"
* @method removeSlashIfLast
* @param {string} string The string to modify
* @return {string} The modified string
*/
priv
.
removeSlashIfLast
=
function
(
string
)
{
if
(
string
[
string
.
length
-
1
]
===
"
/
"
)
{
return
string
.
slice
(
0
,
-
1
);
}
return
string
;
};
/**
* Modify an ajax object to add default values
* @method makeAjaxObject
* @param {object} json The JSON object
* @param {object} option The option object
* @param {string} method The erp5 request method
* @param {object} ajax_object The ajax object to override
* @return {object} A new ajax object with default values
*/
priv
.
makeAjaxObject
=
function
(
json
,
option
,
method
,
ajax_object
)
{
ajax_object
.
type
=
"
POST
"
;
ajax_object
.
dataType
=
"
json
"
;
ajax_object
.
data
=
[
{
"
name
"
:
"
doc
"
,
"
value
"
:
JSON
.
stringify
(
json
)},
{
"
name
"
:
"
option
"
,
"
value
"
:
JSON
.
stringify
(
option
)},
{
"
name
"
:
"
mode
"
,
"
value
"
:
priv
.
mode
}
];
ajax_object
.
url
=
priv
.
url
+
"
/JIO_
"
+
method
+
"
?
"
+
priv
.
encoded_login
+
"
_=
"
+
Date
.
now
();
ajax_object
.
async
=
ajax_object
.
async
===
false
?
false
:
true
;
ajax_object
.
crossdomain
=
ajax_object
.
crossdomain
===
false
?
false
:
true
;
ajax_object
.
headers
=
ajax_object
.
headers
||
{};
return
ajax_object
;
};
/**
* Runs all ajax requests for erp5Storage
* @method ajax
* @param {object} json The JSON object
* @param {object} option The option object
* @param {string} method The erp5 request method
* @param {object} ajax_object The request parameters (optional)
*/
priv
.
ajax
=
function
(
json
,
option
,
method
,
ajax_object
)
{
return
$
.
ajax
(
priv
.
makeAjaxObject
(
json
,
option
,
method
,
ajax_object
||
{}));
//.always(then || function () {});
};
/**
* Creates error objects for this storage
* @method createError
* @param {string} url url to clean up
* @return {object} error The error object
*/
priv
.
createError
=
function
(
status
,
message
,
reason
)
{
var
error
=
{
"
status
"
:
status
,
"
message
"
:
message
,
"
reason
"
:
reason
};
switch
(
status
)
{
case
404
:
error
.
statusText
=
"
Not found
"
;
break
;
case
405
:
error
.
statusText
=
"
Method Not Allowed
"
;
break
;
case
409
:
error
.
statusText
=
"
Conflicts
"
;
break
;
case
24
:
error
.
statusText
=
"
Corrupted Document
"
;
break
;
}
error
.
error
=
error
.
statusText
.
toLowerCase
().
split
(
"
"
).
join
(
"
_
"
);
return
error
;
};
/**
* Converts ajax error object to a JIO error object
* @method ajaxErrorToJioError
* @param {object} ajax_error_object The ajax error object
* @param {string} message The error message
* @param {string} reason The error reason
* @return {object} The JIO error object
*/
priv
.
ajaxErrorToJioError
=
function
(
ajax_error_object
,
message
,
reason
)
{
var
jio_error_object
=
{};
jio_error_object
.
status
=
ajax_error_object
.
status
;
jio_error_object
.
statusText
=
ajax_error_object
.
statusText
;
jio_error_object
.
error
=
ajax_error_object
.
statusText
.
toLowerCase
().
split
(
"
"
).
join
(
"
_
"
);
jio_error_object
.
message
=
message
;
jio_error_object
.
reason
=
reason
;
return
jio_error_object
;
};
/**
* Function that create an object containing jQuery like callbacks
* @method makeJQLikeCallback
* @return {object} jQuery like callback methods
*/
priv
.
makeJQLikeCallback
=
function
()
{
var
result
=
null
,
emptyFun
=
function
()
{},
jql
=
{
"
respond
"
:
function
()
{
result
=
arguments
;
},
"
to_return
"
:
{
"
always
"
:
function
(
func
)
{
if
(
result
)
{
func
.
apply
(
func
,
result
);
jql
.
to_return
.
always
=
emptyFun
;
}
else
{
jql
.
respond
=
func
;
}
return
jql
.
to_return
;
}
}
};
return
jql
;
};
/**
* Use option object and converts a query to a compatible ERP5 Query.
*
* @param {Object} option The command options
*/
priv
.
convertToErp5Query
=
function
(
option
)
{
option
.
query
=
complex_queries
.
QueryFactory
.
create
(
option
.
query
||
""
);
if
(
option
.
wildcard_character
===
undefined
||
(
option
.
wildcard_character
!==
null
&&
typeof
option
.
wildcard_character
!==
'
string
'
))
{
option
.
wildcard_character
=
'
%
'
;
}
else
{
option
.
wildcard_character
=
option
.
wildcard_character
||
''
;
}
option
.
query
.
onParseSimpleQuery
=
function
(
object
)
{
if
(
option
.
wildcard_character
.
length
===
1
&&
object
.
parsed
.
operator
===
'
=
'
)
{
object
.
parsed
.
operator
=
'
like
'
;
if
(
option
.
wildcard_character
===
'
%
'
)
{
object
.
parsed
.
value
=
object
.
parsed
.
value
.
replace
(
/_/g
,
'
\\
_
'
);
}
else
if
(
option
.
wildcard_character
===
'
_
'
)
{
object
.
parsed
.
value
=
object
.
parsed
.
value
.
replace
(
/%/g
,
'
\\
%
'
).
replace
(
/_/g
,
'
%
'
);
}
else
{
object
.
parsed
.
value
=
object
.
parsed
.
value
.
replace
(
/
([
%_
])
/g
,
'
\\
$1
'
).
replace
(
new
RegExp
(
complex_queries
.
stringEscapeRegexpCharacters
(
option
.
wildcard_character
),
'
g
'
),
'
%
'
);
}
}
};
option
.
query
=
option
.
query
.
parse
();
};
// ERP5 REQUESTS //
/**
* Sends a request to ERP5
* @method erp5.genericRequest
* @param {object} doc The document object
* @param {object} option The option object
* @param {string} method The ERP5 request method
*/
erp5
.
genericRequest
=
function
(
json
,
option
,
method
)
{
var
jql
=
priv
.
makeJQLikeCallback
(),
error
=
null
;
priv
.
ajax
(
json
,
option
,
method
).
always
(
function
(
one
,
state
,
three
)
{
if
(
state
===
"
parsererror
"
)
{
return
jql
.
respond
(
priv
.
createError
(
24
,
"
Cannot parse data
"
,
"
Corrupted data
"
),
undefined
);
}
if
(
state
!==
"
success
"
)
{
error
=
priv
.
ajaxErrorToJioError
(
one
,
"
An error occured on
"
+
method
,
"
Unknown
"
);
if
(
one
.
status
===
404
)
{
error
.
reason
=
"
Not Found
"
;
}
return
jql
.
respond
(
error
,
undefined
);
}
if
(
one
.
err
!==
null
)
{
return
jql
.
respond
(
one
.
err
,
undefined
);
}
if
(
one
.
response
!==
null
)
{
return
jql
.
respond
(
undefined
,
one
.
response
);
}
return
jql
.
respond
(
priv
.
createError
(
24
,
"
Cannot parse data
"
,
"
Corrupted data
"
),
undefined
);
});
return
jql
.
to_return
;
};
// JIO COMMANDS //
/**
* The ERP5 storage generic command
* @method genericCommand
* @param {object} command The JIO command object
* @param {string} method The ERP5 request method
*/
priv
.
genericCommand
=
function
(
command
,
method
)
{
var
option
=
command
.
cloneOption
();
if
(
complex_queries
!==
undefined
&&
method
===
'
allDocs
'
&&
option
.
query
)
{
priv
.
convertToErp5Query
(
option
);
}
erp5
.
genericRequest
(
command
.
cloneDoc
(),
option
,
method
).
always
(
function
(
err
,
response
)
{
if
(
err
)
{
return
that
.
error
(
err
);
}
return
that
.
success
(
response
);
});
};
/**
* Creates a new document
* @method post
* @param {object} command The JIO command
*/
that
.
post
=
function
(
command
)
{
priv
.
genericCommand
(
command
,
"
post
"
);
};
/**
* Creates or updates a document
* @method put
* @param {object} command The JIO command
*/
that
.
put
=
function
(
command
)
{
priv
.
genericCommand
(
command
,
"
put
"
);
};
/**
* Add an attachment to a document
* @method putAttachment
* @param {object} command The JIO command
*/
that
.
putAttachment
=
function
(
command
)
{
priv
.
genericCommand
(
command
,
"
putAttachment
"
);
};
/**
* Get a document
* @method get
* @param {object} command The JIO command
*/
that
.
get
=
function
(
command
)
{
priv
.
genericCommand
(
command
,
"
get
"
);
};
/**
* Get an attachment
* @method getAttachment
* @param {object} command The JIO command
*/
that
.
getAttachment
=
function
(
command
)
{
priv
.
genericCommand
(
command
,
"
getAttachment
"
);
};
/**
* Remove a document
* @method remove
* @param {object} command The JIO command
*/
that
.
remove
=
function
(
command
)
{
priv
.
genericCommand
(
command
,
"
remove
"
);
};
/**
* Remove an attachment
* @method removeAttachment
* @param {object} command The JIO command
*/
that
.
removeAttachment
=
function
(
command
)
{
priv
.
genericCommand
(
command
,
"
removeAttachment
"
);
};
/**
* Gets a document list from a distant erp5 storage
* Options:
* - {boolean} include_docs Also retrieve the actual document content.
* @method allDocs
* @param {object} command The JIO command
*/
that
.
allDocs
=
function
(
command
)
{
priv
.
genericCommand
(
command
,
"
allDocs
"
);
};
/**
* Checks a document state
* @method check
* @param {object} command The JIO command
*/
that
.
check
=
function
(
command
)
{
priv
.
genericCommand
(
command
,
"
check
"
);
};
/**
* Restore a document state to a coherent state
* @method repair
* @param {object} command The JIO command
*/
that
.
repair
=
function
(
command
)
{
priv
.
genericCommand
(
command
,
"
repair
"
);
};
priv
.
__init__
(
spec
);
return
that
;
});
src/jio.storage/gidstorage.js
deleted
100644 → 0
View file @
3356688e
/*
* JIO extension for resource global identifier management.
* Copyright (C) 2013 Nexedi SA
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global define, jIO, setTimeout */
/**
* JIO GID Storage. Type = 'gid'.
* Identifies document with their global identifier representation
*
* Sub storages must support complex queries and include_docs options.
*
* Storage Description:
*
* {
* "type": "gid",
* "sub_storage": {<storage description>},
* "constraints": {
* "default": {
* "identifier": "list", // ['a', 1]
* "type": "DCMIType", // 'Text'
* "title": "string" // 'something blue'
* },
* "Text": {
* "format": "contentType" // contains 'text/plain;charset=utf-8'
* },
* "Image": {
* "version": "json" // value as is
* }
* }
* }
*/
// define([module_name], [dependencies], module);
(
function
(
dependencies
,
module
)
{
"
use strict
"
;
if
(
typeof
define
===
'
function
'
&&
define
.
amd
)
{
return
define
(
dependencies
,
module
);
}
module
(
jIO
);
}([
'
jio
'
],
function
(
jIO
)
{
"
use strict
"
;
var
dcmi_types
,
metadata_actions
,
content_type_re
;
dcmi_types
=
{
'
Collection
'
:
'
Collection
'
,
'
Dataset
'
:
'
Dataset
'
,
'
Event
'
:
'
Event
'
,
'
Image
'
:
'
Image
'
,
'
InteractiveResource
'
:
'
InteractiveResource
'
,
'
MovingImage
'
:
'
MovingImage
'
,
'
PhysicalObject
'
:
'
PhysicalObject
'
,
'
Service
'
:
'
Service
'
,
'
Software
'
:
'
Software
'
,
'
Sound
'
:
'
Sound
'
,
'
StillImage
'
:
'
StillImage
'
,
'
Text
'
:
'
Text
'
};
metadata_actions
=
{
/**
* Returns the metadata value
*/
json
:
function
(
value
)
{
return
value
;
},
/**
* Returns the metadata if there is a string
*/
string
:
function
(
value
)
{
if
(
!
Array
.
isArray
(
value
))
{
if
(
typeof
value
===
'
object
'
)
{
return
value
.
content
;
}
return
value
;
}
},
/**
* Returns the metadata in a array format
*/
list
:
function
(
value
)
{
var
i
,
new_value
=
[];
if
(
Array
.
isArray
(
value
))
{
for
(
i
=
0
;
i
<
value
.
length
;
i
+=
1
)
{
if
(
typeof
value
[
i
]
===
'
object
'
)
{
new_value
[
new_value
.
length
]
=
value
[
i
].
content
;
}
else
{
new_value
[
new_value
.
length
]
=
value
[
i
];
}
}
}
else
if
(
value
!==
undefined
)
{
value
=
[
value
];
}
return
value
;
},
/**
* Returns the metadata if there is a string equal to a DCMIType
*/
DCMIType
:
function
(
value
)
{
var
i
;
if
(
!
Array
.
isArray
(
value
))
{
value
=
[
value
];
}
for
(
i
=
0
;
i
<
value
.
length
;
i
+=
1
)
{
if
(
typeof
value
[
i
]
===
'
object
'
&&
dcmi_types
[
value
[
i
].
content
])
{
return
value
[
i
].
content
;
}
if
(
dcmi_types
[
value
[
i
]])
{
return
value
[
i
];
}
}
},
/**
* Returns the metadata content type if exist
*/
contentType
:
function
(
value
)
{
var
i
;
if
(
!
Array
.
isArray
(
value
))
{
value
=
[
value
];
}
for
(
i
=
0
;
i
<
value
.
length
;
i
+=
1
)
{
if
(
value
[
i
]
===
'
object
'
)
{
if
(
content_type_re
.
test
(
value
[
i
].
content
))
{
return
value
[
i
].
content
;
}
}
else
{
if
(
content_type_re
.
test
(
value
[
i
]))
{
return
value
[
i
];
}
}
}
},
/**
* Returns the metadata if it is a date
*/
date
:
function
(
value
)
{
var
d
;
if
(
!
Array
.
isArray
(
value
))
{
if
(
typeof
value
===
'
object
'
)
{
d
=
new
Date
(
value
.
content
);
value
=
value
.
content
;
}
else
{
d
=
new
Date
(
value
);
}
}
if
(
Object
.
prototype
.
toString
.
call
(
d
)
===
"
[object Date]
"
)
{
if
(
!
isNaN
(
d
.
getTime
()))
{
return
value
;
}
}
}
};
content_type_re
=
/^
([
a-z
]
+
\/[
a-zA-Z0-9
\+\-\.]
+
)(?:\s
*;
\s
*charset
\s
*=
\s
*
([
a-zA-Z0-9
\-]
+
))?
$/
;
/**
* Creates a gid from metadata and constraints.
*
* @param {Object} metadata The metadata to use
* @param {Object} constraints The constraints
* @return {String} The gid or undefined if metadata doesn't respect the
* constraints
*/
function
gidFormat
(
metadata
,
constraints
)
{
var
types
,
i
,
j
,
meta_key
,
result
=
[],
tmp
,
constraint
,
actions
;
types
=
(
metadata_actions
.
list
(
metadata
.
type
)
||
[]).
slice
();
types
.
unshift
(
'
default
'
);
for
(
i
=
0
;
i
<
types
.
length
;
i
+=
1
)
{
constraint
=
constraints
[
types
[
i
]];
for
(
meta_key
in
constraint
)
{
if
(
constraint
.
hasOwnProperty
(
meta_key
))
{
actions
=
constraint
[
meta_key
];
if
(
!
Array
.
isArray
(
actions
))
{
actions
=
[
actions
];
}
for
(
j
=
0
;
j
<
actions
.
length
;
j
+=
1
)
{
tmp
=
metadata_actions
[
actions
[
j
]
](
metadata
[
meta_key
]);
if
(
tmp
===
undefined
)
{
return
;
}
}
result
[
result
.
length
]
=
[
meta_key
,
tmp
];
}
}
}
// sort dict keys to make gid universal
result
.
sort
(
function
(
a
,
b
)
{
return
a
[
0
]
<
b
[
0
]
?
-
1
:
a
[
0
]
>
b
[
0
]
?
1
:
0
;
});
tmp
=
{};
for
(
i
=
0
;
i
<
result
.
length
;
i
+=
1
)
{
tmp
[
result
[
i
][
0
]]
=
result
[
i
][
1
];
}
return
JSON
.
stringify
(
tmp
);
}
/**
* Convert a gid to a complex query.
*
* @param {Object,String} gid The gid
* @param {Object} constraints The constraints
* @return {Object} A complex serialized object
*/
function
gidToComplexQuery
(
gid
,
contraints
)
{
var
k
,
i
,
result
=
[],
meta
,
content
;
if
(
typeof
gid
===
'
string
'
)
{
gid
=
JSON
.
parse
(
gid
);
}
for
(
k
in
gid
)
{
if
(
gid
.
hasOwnProperty
(
k
))
{
meta
=
gid
[
k
];
if
(
!
Array
.
isArray
(
meta
))
{
meta
=
[
meta
];
}
for
(
i
=
0
;
i
<
meta
.
length
;
i
+=
1
)
{
content
=
meta
[
i
];
if
(
typeof
content
===
'
object
'
)
{
content
=
content
.
content
;
}
result
[
result
.
length
]
=
{
"
type
"
:
"
simple
"
,
"
operator
"
:
"
=
"
,
"
key
"
:
k
,
"
value
"
:
content
};
}
}
}
return
{
"
type
"
:
"
complex
"
,
"
operator
"
:
"
AND
"
,
"
query_list
"
:
result
};
}
/**
* Parse the gid and returns a metadata object containing gid keys and values.
*
* @param {String} gid The gid to convert
* @param {Object} constraints The constraints
* @return {Object} The gid metadata
*/
function
gidParse
(
gid
,
constraints
)
{
var
object
;
try
{
object
=
JSON
.
parse
(
gid
);
}
catch
(
e
)
{
return
;
}
if
(
gid
!==
gidFormat
(
object
,
constraints
))
{
return
;
}
return
object
;
}
/**
* The gid storage used by JIO.
*
* This storage change the id of a document with its global id. A global id
* is representation of a document metadata used to define it as uniq. The way
* to generate global ids can be define in the storage description. It allows
* us use duplicating storage with different sub storage kind.
*
* @class gidStorage
*/
function
gidStorage
(
spec
,
my
)
{
var
that
=
my
.
basicStorage
(
spec
,
my
),
priv
=
{};
priv
.
sub_storage
=
spec
.
sub_storage
;
priv
.
constraints
=
spec
.
constraints
||
{
"
default
"
:
{
"
type
"
:
"
DCMIType
"
,
"
title
"
:
"
string
"
}
};
// Overrides
that
.
specToStore
=
function
()
{
return
{
"
sub_storage
"
:
priv
.
sub_storage
,
"
constraints
"
:
priv
.
constraints
};
};
// JIO Commands
/**
* Generic command for post or put one.
*
* This command will check if the document already exist with an allDocs
* and a complex query. If exist, then post will fail. Put will update the
* retrieved document thanks to its real id. If no documents are found, post
* and put will create a new document with the sub storage id generator.
*
* @method putOrPost
* @private
* @param {Command} command The JIO command
* @param {String} method The command method
*/
priv
.
putOrPost
=
function
(
command
,
method
)
{
setTimeout
(
function
()
{
var
gid
,
complex_query
,
doc
=
command
.
cloneDoc
();
gid
=
gidFormat
(
doc
,
priv
.
constraints
);
if
(
gid
===
undefined
||
(
doc
.
_id
&&
gid
!==
doc
.
_id
))
{
return
that
.
error
({
"
status
"
:
400
,
"
statusText
"
:
"
Bad Request
"
,
"
error
"
:
"
bad_request
"
,
"
message
"
:
"
Cannot
"
+
method
+
"
document
"
,
"
reason
"
:
"
metadata should respect constraints
"
});
}
complex_query
=
gidToComplexQuery
(
gid
);
that
.
addJob
(
'
allDocs
'
,
priv
.
sub_storage
,
{},
{
"
query
"
:
complex_query
,
"
wildcard_character
"
:
null
},
function
(
response
)
{
var
update_method
=
method
;
if
(
response
.
total_rows
!==
0
)
{
if
(
method
===
'
post
'
)
{
return
that
.
error
({
"
status
"
:
409
,
"
statusText
"
:
"
Conflict
"
,
"
error
"
:
"
conflict
"
,
"
message
"
:
"
Cannot
"
+
method
+
"
document
"
,
"
reason
"
:
"
Document already exist
"
});
}
doc
=
command
.
cloneDoc
();
doc
.
_id
=
response
.
rows
[
0
].
id
;
}
else
{
doc
=
command
.
cloneDoc
();
delete
doc
.
_id
;
update_method
=
'
post
'
;
}
that
.
addJob
(
update_method
,
priv
.
sub_storage
,
doc
,
{
},
function
(
response
)
{
response
.
id
=
gid
;
that
.
success
(
response
);
},
function
(
err
)
{
err
.
message
=
"
Cannot
"
+
method
+
"
document
"
;
that
.
error
(
err
);
});
},
function
(
err
)
{
err
.
message
=
"
Cannot
"
+
method
+
"
document
"
;
that
.
error
(
err
);
});
});
};
/**
* Generic command for putAttachment, getAttachment or removeAttachment.
*
* This command will check if the document exist with an allDocs and a
* complex query. If not exist, then it returns 404. Otherwise the
* action will be done on the attachment of the found document.
*
* @method putGetOrRemoveAttachment
* @private
* @param {Command} command The JIO command
* @param {String} method The command method
*/
priv
.
putGetOrRemoveAttachment
=
function
(
command
,
method
)
{
setTimeout
(
function
()
{
var
gid_object
,
complex_query
,
doc
=
command
.
cloneDoc
();
gid_object
=
gidParse
(
doc
.
_id
,
priv
.
constraints
);
if
(
gid_object
===
undefined
)
{
return
that
.
error
({
"
status
"
:
400
,
"
statusText
"
:
"
Bad Request
"
,
"
error
"
:
"
bad_request
"
,
"
message
"
:
"
Cannot
"
+
method
+
"
attachment
"
,
"
reason
"
:
"
metadata should respect constraints
"
});
}
complex_query
=
gidToComplexQuery
(
gid_object
);
that
.
addJob
(
'
allDocs
'
,
priv
.
sub_storage
,
{},
{
"
query
"
:
complex_query
,
"
wildcard_character
"
:
null
},
function
(
response
)
{
if
(
response
.
total_rows
===
0
)
{
return
that
.
error
({
"
status
"
:
404
,
"
statusText
"
:
"
Not Found
"
,
"
error
"
:
"
not_found
"
,
"
message
"
:
"
Cannot
"
+
method
+
"
attachment
"
,
"
reason
"
:
"
Document already exist
"
});
}
gid_object
=
doc
.
_id
;
doc
.
_id
=
response
.
rows
[
0
].
id
;
that
.
addJob
(
method
+
"
Attachment
"
,
priv
.
sub_storage
,
doc
,
{
},
function
(
response
)
{
if
(
method
!==
'
get
'
)
{
response
.
id
=
gid_object
;
}
that
.
success
(
response
);
},
function
(
err
)
{
err
.
message
=
"
Cannot
"
+
method
+
"
attachment
"
;
that
.
error
(
err
);
});
},
function
(
err
)
{
err
.
message
=
"
Cannot
"
+
method
+
"
attachment
"
;
that
.
error
(
err
);
});
});
};
/**
* See {{#crossLink "gidStorage/putOrPost:method"}}{{/#crossLink}}.
*
* @method post
* @param {Command} command The JIO command
*/
that
.
post
=
function
(
command
)
{
priv
.
putOrPost
(
command
,
'
post
'
);
};
/**
* See {{#crossLink "gidStorage/putOrPost:method"}}{{/#crossLink}}.
*
* @method put
* @param {Command} command The JIO command
*/
that
.
put
=
function
(
command
)
{
priv
.
putOrPost
(
command
,
'
put
'
);
};
/**
* Puts an attachment to a document thank to its gid, a sub allDocs and a
* complex query.
*
* @method putAttachment
* @param {Command} command The JIO command
*/
that
.
putAttachment
=
function
(
command
)
{
priv
.
putGetOrRemoveAttachment
(
command
,
'
put
'
);
};
/**
* Gets a document thank to its gid, a sub allDocs and a complex query.
*
* @method get
* @param {Command} command The JIO command
*/
that
.
get
=
function
(
command
)
{
setTimeout
(
function
()
{
var
gid_object
,
complex_query
;
gid_object
=
gidParse
(
command
.
getDocId
(),
priv
.
constraints
);
if
(
gid_object
===
undefined
)
{
return
that
.
error
({
"
status
"
:
400
,
"
statusText
"
:
"
Bad Request
"
,
"
error
"
:
"
bad_request
"
,
"
message
"
:
"
Cannot get document
"
,
"
reason
"
:
"
metadata should respect constraints
"
});
}
complex_query
=
gidToComplexQuery
(
gid_object
);
that
.
addJob
(
'
allDocs
'
,
priv
.
sub_storage
,
{},
{
"
query
"
:
complex_query
,
"
wildcard_character
"
:
null
,
"
include_docs
"
:
true
},
function
(
response
)
{
if
(
response
.
total_rows
===
0
)
{
return
that
.
error
({
"
status
"
:
404
,
"
statusText
"
:
"
Not Found
"
,
"
error
"
:
"
not_found
"
,
"
message
"
:
"
Cannot get document
"
,
"
reason
"
:
"
missing
"
});
}
response
.
rows
[
0
].
doc
.
_id
=
command
.
getDocId
();
return
that
.
success
(
response
.
rows
[
0
].
doc
);
},
function
(
err
)
{
err
.
message
=
"
Cannot get document
"
;
return
that
.
error
(
err
);
});
});
};
/**
* Gets an attachment from a document thank to its gid, a sub allDocs and a
* complex query.
*
* @method getAttachment
* @param {Command} command The JIO command
*/
that
.
getAttachment
=
function
(
command
)
{
priv
.
putGetOrRemoveAttachment
(
command
,
'
get
'
);
};
/**
* Remove a document thank to its gid, sub allDocs and a complex query.
*
* @method remove
* @param {Command} command The JIO command.
*/
that
.
remove
=
function
(
command
)
{
setTimeout
(
function
()
{
var
gid_object
,
complex_query
,
doc
=
command
.
cloneDoc
();
gid_object
=
gidParse
(
doc
.
_id
,
priv
.
constraints
);
if
(
gid_object
===
undefined
)
{
return
that
.
error
({
"
status
"
:
400
,
"
statusText
"
:
"
Bad Request
"
,
"
error
"
:
"
bad_request
"
,
"
message
"
:
"
Cannot remove document
"
,
"
reason
"
:
"
metadata should respect constraints
"
});
}
complex_query
=
gidToComplexQuery
(
gid_object
);
that
.
addJob
(
'
allDocs
'
,
priv
.
sub_storage
,
{},
{
"
query
"
:
complex_query
,
"
wildcard_character
"
:
null
},
function
(
response
)
{
if
(
response
.
total_rows
===
0
)
{
return
that
.
error
({
"
status
"
:
404
,
"
statusText
"
:
"
Not found
"
,
"
error
"
:
"
not_found
"
,
"
message
"
:
"
Cannot remove document
"
,
"
reason
"
:
"
missing
"
});
}
gid_object
=
doc
.
_id
;
doc
=
{
"
_id
"
:
response
.
rows
[
0
].
id
};
that
.
addJob
(
'
remove
'
,
priv
.
sub_storage
,
doc
,
{
},
function
(
response
)
{
response
.
id
=
gid_object
;
that
.
success
(
response
);
},
function
(
err
)
{
err
.
message
=
"
Cannot remove document
"
;
that
.
error
(
err
);
});
},
function
(
err
)
{
err
.
message
=
"
Cannot remove document
"
;
that
.
error
(
err
);
});
});
};
/**
* Removes an attachment to a document thank to its gid, a sub allDocs and a
* complex query.
*
* @method removeAttachment
* @param {Command} command The JIO command
*/
that
.
removeAttachment
=
function
(
command
)
{
priv
.
putGetOrRemoveAttachment
(
command
,
'
remove
'
);
};
/**
* Retrieve a list of document which respect gid constraints.
*
* @method allDocs
* @param {Command} command The JIO command
*/
that
.
allDocs
=
function
(
command
)
{
setTimeout
(
function
()
{
var
options
=
command
.
cloneOption
(),
include_docs
;
include_docs
=
options
.
include_docs
;
options
.
include_docs
=
true
;
that
.
addJob
(
'
allDocs
'
,
priv
.
sub_storage
,
{
},
options
,
function
(
response
)
{
var
result
=
[],
doc_gids
=
{},
i
,
row
,
gid
;
while
((
row
=
response
.
rows
.
shift
())
!==
undefined
)
{
if
((
gid
=
gidFormat
(
row
.
doc
,
priv
.
constraints
))
!==
undefined
)
{
if
(
!
doc_gids
[
gid
])
{
doc_gids
[
gid
]
=
true
;
row
.
id
=
gid
;
delete
row
.
key
;
result
[
result
.
length
]
=
row
;
if
(
include_docs
===
true
)
{
row
.
doc
.
_id
=
gid
;
}
else
{
delete
row
.
doc
;
}
}
}
}
doc_gids
=
undefined
;
// free memory
row
=
undefined
;
that
.
success
({
"
total_rows
"
:
result
.
length
,
"
rows
"
:
result
});
},
function
(
err
)
{
err
.
message
=
"
Cannot get all documents
"
;
return
that
.
error
(
err
);
});
});
};
return
that
;
}
jIO
.
addStorageType
(
'
gid
'
,
gidStorage
);
}));
src/jio.storage/indexstorage.js
deleted
100644 → 0
View file @
3356688e
/*
* JIO extension for resource indexing.
* Copyright (C) 2013 Nexedi SA
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, regexp: true */
/*global jIO, define, complex_queries */
/**
* JIO Index Storage.
* Manages indexes for specified storages.
* Description:
* {
* "type": "index",
* "indices": [{
* "id": "index_title_subject.json", // doc id where to store indices
* "index": ["title", "subject"], // metadata to index
* "attachment": "youhou", // default "body"
* "metadata": { // default {}
* "type": "Dataset",
* "format": "application/json",
* "date": "yyyy-mm-ddTHH:MM:SS+HH:MM",
* "title": "My index database",
* "creator": "Me"
* },
* "sub_storage": <sub storage where to store index>
* (default equal to parent sub_storage field)
* }, {
* "id": "index_year.json",
* "index": "year"
* ...
* }],
* "sub_storage": <sub storage description>
* }
*
* Sent document metadata will be:
* index_titre_subject.json
* {
* "_id": "index_title_subject.json",
* "type": "Dataset",
* "format": "application/json",
* "date": "yyyy-mm-ddTHH:MM:SS+HH:MM",
* "title": "My index database",
* "creator": "Me",
* "_attachments": {
* "youhou": {
* "length": Num,
* "digest": "XXX",
* "content_type": "application/json"
* }
* }
* }
* Attachment "youhou"
* {
* "indexing": ["title", "subject"],
* "free": [0],
* "location": {
* "foo": 1,
* "bar": 2,
* ...
* },
* "database": [
* {},
* {"_id": "foo", "title": "...", "subject": ...},
* {"_id": "bar", "title": "...", "subject": ...},
* ...
* ]
* }
*
* index_year.json
* {
* "_id": "index_year.json",
* "_attachments": {
* "body": {..}
* }
* }
* Attachment "body"
* {
* "indexing": ["year"],
* "free": [1],
* "location": {
* "foo": 0,
* "bar": 2,
* ...
* },
* "database": [
* {"_id": "foo", "year": "..."},
* {},
* {"_id": "bar", "year": "..."},
* ...
* ]
* }
*
* A put document will be indexed to the free location if exist, else it will be
* indexed at the end of the database. The document id will be indexed, also, in
* 'location' to quickly replace metadata.
*
* Only one or two loops are executed:
* - one to filter retrieved document list (no query -> no loop)
* - one to format the result to a JIO response
*/
// define([module_name], [dependencies], module);
(
function
(
dependencies
,
module
)
{
"
use strict
"
;
if
(
typeof
define
===
'
function
'
&&
define
.
amd
)
{
return
define
(
dependencies
,
module
);
}
module
(
jIO
,
complex_queries
);
}([
'
jio
'
,
'
complex_queries
'
],
function
(
jIO
,
complex_queries
)
{
"
use strict
"
;
var
error_dict
=
{
"
Corrupted Index
"
:
{
"
status
"
:
24
,
"
statusText
"
:
"
Corrupt
"
,
"
error
"
:
"
corrupt
"
,
"
reason
"
:
"
corrupted index database
"
},
"
Corrupted Metadata
"
:
{
"
status
"
:
24
,
"
statusText
"
:
"
Corrupt
"
,
"
error
"
:
"
corrupt
"
,
"
reason
"
:
"
corrupted document
"
},
"
Not Found
"
:
{
"
status
"
:
404
,
"
statusText
"
:
"
Not Found
"
,
"
error
"
:
"
not_found
"
,
"
reason
"
:
"
missing document
"
},
"
Conflict
"
:
{
"
status
"
:
409
,
"
statusText
"
:
"
Conflicts
"
,
"
error
"
:
"
conflicts
"
,
"
reason
"
:
"
already exist
"
},
"
Different Index
"
:
{
"
status
"
:
40
,
"
statusText
"
:
"
Check failed
"
,
"
error
"
:
"
check_failed
"
,
"
reason
"
:
"
incomplete database
"
}
};
/**
* Generate a JIO Error Object
*
* @method generateErrorObject
* @param {String} name The error name
* @param {String} message The error message
* @param {String} [reason] The error reason
* @return {Object} A jIO error object
*/
function
generateErrorObject
(
name
,
message
,
reason
)
{
if
(
!
error_dict
[
name
])
{
return
{
"
status
"
:
0
,
"
statusText
"
:
"
Unknown
"
,
"
error
"
:
"
unknown
"
,
"
message
"
:
message
,
"
reason
"
:
reason
||
"
unknown
"
};
}
return
{
"
status
"
:
error_dict
[
name
].
status
,
"
statusText
"
:
error_dict
[
name
].
statusText
,
"
error
"
:
error_dict
[
name
].
error
,
"
message
"
:
message
,
"
reason
"
:
reason
||
error_dict
[
name
].
reason
};
}
/**
* Get the real type of an object
* @method type
* @param {Any} value The value to check
* @return {String} The value type
*/
function
type
(
value
)
{
// returns "String", "Object", "Array", "RegExp", ...
return
(
/^
\[
object
([
a-zA-Z
]
+
)\]
$/
).
exec
(
Object
.
prototype
.
toString
.
call
(
value
)
)[
1
];
}
/**
* Generate a new uuid
* @method generateUuid
* @return {string} The new uuid
*/
function
generateUuid
()
{
var
S4
=
function
()
{
var
i
,
string
=
Math
.
floor
(
Math
.
random
()
*
0x10000
/* 65536 */
).
toString
(
16
);
for
(
i
=
string
.
length
;
i
<
4
;
i
+=
1
)
{
string
=
"
0
"
+
string
;
}
return
string
;
};
return
S4
()
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
S4
()
+
S4
();
}
/**
* Tool to get the date in W3C date format "2011-12-13T14:15:16+01:00"
*
* @param {Any} date The new Date() parameter
* @return {String} The date in W3C date format
*/
function
w3cDate
(
date
)
{
var
d
=
new
Date
(
date
),
offset
=
-
d
.
getTimezoneOffset
();
return
(
d
.
getFullYear
()
+
"
-
"
+
(
d
.
getMonth
()
+
1
)
+
"
-
"
+
d
.
getDate
()
+
"
T
"
+
d
.
getHours
()
+
"
:
"
+
d
.
getMinutes
()
+
"
:
"
+
d
.
getSeconds
()
+
(
offset
<
0
?
"
-
"
:
"
+
"
)
+
(
offset
/
60
)
+
"
:
"
+
(
offset
%
60
)
).
replace
(
/
[
0-9
]
+/g
,
function
(
found
)
{
if
(
found
.
length
<
2
)
{
return
'
0
'
+
found
;
}
return
found
;
});
}
/**
* A JSON Index manipulator
*
* @class JSONIndex
* @constructor
*/
function
JSONIndex
(
spec
)
{
var
that
=
this
;
spec
=
spec
||
{};
/**
* The document id
*
* @property _id
* @type String
*/
that
.
_id
=
spec
.
_id
;
/**
* The attachment id
*
* @property _attachment
* @type String
*/
that
.
_attachment
=
spec
.
_attachment
;
/**
* The array with metadata key to index
*
* @property _indexing
* @type Array
*/
that
.
_indexing
=
spec
.
indexing
||
[];
/**
* The array of free location index
*
* @property _free
* @type Array
* @default []
*/
that
.
_free
=
spec
.
free
||
[];
/**
* The dictionnary document id -> database index
*
* @property _location
* @type Object
* @default {}
*/
that
.
_location
=
spec
.
location
||
{};
/**
* The database array containing document metadata
*
* @property _database
* @type Array
* @default []
*/
that
.
_database
=
spec
.
database
||
[];
/**
* Adds a metadata object in the database, replace if already exist
*
* @method put
* @param {Object} meta The metadata to add
* @return {Boolean} true if added, false otherwise
*/
that
.
put
=
function
(
meta
)
{
var
k
,
needed_meta
=
{},
ok
=
false
;
if
(
typeof
meta
.
_id
!==
"
string
"
&&
meta
.
_id
!==
""
)
{
throw
new
TypeError
(
"
Corrupted Metadata
"
);
}
for
(
k
in
meta
)
{
if
(
meta
.
hasOwnProperty
(
k
))
{
if
(
k
[
0
]
===
"
_
"
)
{
if
(
k
===
"
_id
"
)
{
needed_meta
[
k
]
=
meta
[
k
];
}
}
else
if
(
that
.
_indexing_object
[
k
])
{
needed_meta
[
k
]
=
meta
[
k
];
ok
=
true
;
}
}
}
if
(
ok
)
{
if
(
typeof
that
.
_location
[
meta
.
_id
]
===
"
number
"
)
{
that
.
_database
[
that
.
_location
[
meta
.
_id
]]
=
needed_meta
;
}
else
if
(
that
.
_free
.
length
>
0
)
{
k
=
that
.
_free
.
shift
();
that
.
_database
[
k
]
=
needed_meta
;
that
.
_location
[
meta
.
_id
]
=
k
;
}
else
{
that
.
_database
.
push
(
needed_meta
);
that
.
_location
[
meta
.
_id
]
=
that
.
_database
.
length
-
1
;
}
return
true
;
}
if
(
typeof
that
.
_location
[
meta
.
_id
]
===
"
number
"
)
{
that
.
remove
(
meta
);
}
return
false
;
};
/**
* Removes a metadata object from the database if exist
*
* @method remove
* @param {Object} meta The metadata to remove
*/
that
.
remove
=
function
(
meta
)
{
if
(
typeof
meta
.
_id
!==
"
string
"
)
{
throw
new
TypeError
(
"
Corrupted Metadata
"
);
}
if
(
typeof
that
.
_location
[
meta
.
_id
]
!==
"
number
"
)
{
throw
new
ReferenceError
(
"
Not Found
"
);
}
that
.
_database
[
that
.
_location
[
meta
.
_id
]]
=
null
;
that
.
_free
.
push
(
that
.
_location
[
meta
.
_id
]);
delete
that
.
_location
[
meta
.
_id
];
};
/**
* Checks if the index database document is correct
*
* @method check
*/
that
.
check
=
function
()
{
var
id
,
database_meta
;
if
(
typeof
that
.
_id
!==
"
string
"
||
that
.
_id
===
""
||
typeof
that
.
_attachment
!==
"
string
"
||
that
.
_attachment
===
""
||
!
Array
.
isArray
(
that
.
_free
)
||
!
Array
.
isArray
(
that
.
_indexing
)
||
typeof
that
.
_location
!==
'
object
'
||
Array
.
isArray
(
that
.
_location
)
||
!
Array
.
isArray
(
that
.
_database
)
||
that
.
_indexing
.
length
===
0
)
{
throw
new
TypeError
(
"
Corrupted Index
"
);
}
for
(
id
in
that
.
_location
)
{
if
(
that
.
_location
.
hasOwnProperty
(
id
))
{
database_meta
=
that
.
_database
[
that
.
_location
[
id
]];
if
(
type
(
database_meta
)
!==
"
Object
"
||
database_meta
.
_id
!==
id
)
{
throw
new
TypeError
(
"
Corrupted Index
"
);
}
}
}
};
that
.
equals
=
function
(
json_index
)
{
function
equalsDirection
(
a
,
b
)
{
var
k
;
for
(
k
in
a
.
_location
)
{
if
(
a
.
_location
.
hasOwnProperty
(
k
))
{
if
(
b
.
_location
[
k
]
===
undefined
||
JSON
.
stringify
(
b
.
_database
[
b
.
_location
[
k
]])
!==
JSON
.
stringify
(
a
.
_database
[
a
.
_location
[
k
]]))
{
return
false
;
}
}
}
return
true
;
}
if
(
!
equalsDirection
(
that
,
json_index
))
{
return
false
;
}
if
(
!
equalsDirection
(
json_index
,
that
))
{
return
false
;
}
return
true
;
};
that
.
checkDocument
=
function
(
doc
)
{
var
i
,
key
,
db_doc
;
if
(
typeof
that
.
_location
[
doc
.
_id
]
!==
"
number
"
||
(
db_doc
=
that
.
_database
(
that
.
_location
[
doc
.
_id
]).
_id
)
!==
doc
.
_id
)
{
throw
new
TypeError
(
"
Different Index
"
);
}
for
(
i
=
0
;
i
<
that
.
_indexing
.
length
;
i
+=
1
)
{
key
=
that
.
_indexing
[
i
];
if
(
doc
[
key
]
!==
db_doc
[
key
])
{
throw
new
TypeError
(
"
Different Index
"
);
}
}
};
/**
* Recreates database indices and remove free space
*
* @method repair
*/
that
.
repair
=
function
()
{
var
i
=
0
,
meta
;
that
.
_free
=
[];
that
.
_location
=
{};
if
(
type
(
that
.
_database
)
!==
"
Array
"
)
{
that
.
_database
=
[];
}
while
(
i
<
that
.
_database
.
length
)
{
meta
=
that
.
_database
[
i
];
if
(
type
(
meta
)
===
"
Object
"
&&
typeof
meta
.
_id
===
"
string
"
&&
meta
.
_id
!==
""
&&
!
that
.
_location
[
meta
.
_id
])
{
that
.
_location
[
meta
.
_id
]
=
i
;
i
+=
1
;
}
else
{
that
.
_database
.
splice
(
i
,
1
);
}
}
};
/**
* Returns the serialized version of this object (not cloned)
*
* @method serialized
* @return {Object} The serialized version
*/
that
.
serialized
=
function
()
{
return
{
"
indexing
"
:
that
.
_indexing
,
"
free
"
:
that
.
_free
,
"
location
"
:
that
.
_location
,
"
database
"
:
that
.
_database
};
};
that
.
check
();
that
.
_indexing_object
=
{};
that
.
_indexing
.
forEach
(
function
(
meta_key
)
{
that
.
_indexing_object
[
meta_key
]
=
true
;
});
}
/**
* The JIO index storage constructor
*/
function
indexStorage
(
spec
,
my
)
{
var
that
,
priv
=
{};
that
=
my
.
basicStorage
(
spec
,
my
);
priv
.
indices
=
spec
.
indices
;
priv
.
sub_storage
=
spec
.
sub_storage
;
// Overrides
that
.
specToStore
=
function
()
{
return
{
"
indices
"
:
priv
.
indices
,
"
sub_storage
"
:
priv
.
sub_storage
};
};
/**
* Return the similarity percentage (1 >= p >= 0) between two index lists.
*
* @method similarityPercentage
* @param {Array} list_a An index list
* @param {Array} list_b Another index list
* @return {Number} The similarity percentage
*/
priv
.
similarityPercentage
=
function
(
list_a
,
list_b
)
{
var
ai
,
bi
,
count
=
0
;
for
(
ai
=
0
;
ai
<
list_a
.
length
;
ai
+=
1
)
{
for
(
bi
=
0
;
bi
<
list_b
.
length
;
bi
+=
1
)
{
if
(
list_a
[
ai
]
===
list_b
[
bi
])
{
count
+=
1
;
}
}
}
return
count
/
(
list_a
.
length
>
list_b
.
length
?
list_a
.
length
:
list_b
.
length
);
};
/**
* Select the good index to use according to a select list.
*
* @method selectIndex
* @param {Array} select_list An array of strings
* @return {Number} The index index
*/
priv
.
selectIndex
=
function
(
select_list
)
{
var
i
,
tmp
,
selector
=
{
"
index
"
:
0
,
"
similarity
"
:
0
};
for
(
i
=
0
;
i
<
priv
.
indices
.
length
;
i
+=
1
)
{
tmp
=
priv
.
similarityPercentage
(
select_list
,
priv
.
indices
[
i
].
index
);
if
(
tmp
>
selector
.
similarity
)
{
selector
.
index
=
i
;
selector
.
similarity
=
tmp
;
}
}
return
selector
.
index
;
};
/**
* Get a database
*
* @method getIndexDatabase
* @param {Object} option The command option
* @param {Number} number The location in priv.indices
* @param {Function} callback The callback
*/
priv
.
getIndexDatabase
=
function
(
option
,
number
,
callback
)
{
that
.
addJob
(
"
getAttachment
"
,
priv
.
indices
[
number
].
sub_storage
||
priv
.
sub_storage
,
{
"
_id
"
:
priv
.
indices
[
number
].
id
,
"
_attachment
"
:
priv
.
indices
[
number
].
attachment
||
"
body
"
},
option
,
function
(
response
)
{
try
{
response
=
JSON
.
parse
(
response
);
response
.
_id
=
priv
.
indices
[
number
].
id
;
response
.
_attachment
=
priv
.
indices
[
number
].
attachment
||
"
body
"
;
callback
(
new
JSONIndex
(
response
));
}
catch
(
e
)
{
return
that
.
error
(
generateErrorObject
(
e
.
message
,
"
Repair is necessary
"
,
"
corrupt
"
));
}
},
function
(
err
)
{
if
(
err
.
status
===
404
)
{
callback
(
new
JSONIndex
({
"
_id
"
:
priv
.
indices
[
number
].
id
,
"
_attachment
"
:
priv
.
indices
[
number
].
attachment
||
"
body
"
,
"
indexing
"
:
priv
.
indices
[
number
].
index
}));
return
;
}
err
.
message
=
"
Unable to get index database.
"
;
that
.
error
(
err
);
}
);
};
/**
* Gets a list containing all the databases set in the storage description.
*
* @method getIndexDatabaseList
* @param {Object} option The command option
* @param {Function} callback The result callback(database_list)
*/
priv
.
getIndexDatabaseList
=
function
(
option
,
callback
)
{
var
i
,
count
=
0
,
callbacks
=
{},
response_list
=
[];
callbacks
.
error
=
function
(
index
)
{
return
function
(
err
)
{
if
(
err
.
status
===
404
)
{
response_list
[
index
]
=
new
JSONIndex
({
"
_id
"
:
priv
.
indices
[
index
].
id
,
"
_attachment
"
:
priv
.
indices
[
index
].
attachment
||
"
body
"
,
"
indexing
"
:
priv
.
indices
[
index
].
index
});
count
+=
1
;
if
(
count
===
priv
.
indices
.
length
)
{
callback
(
response_list
);
}
return
;
}
err
.
message
=
"
Unable to get index database.
"
;
that
.
error
(
err
);
};
};
callbacks
.
success
=
function
(
index
)
{
return
function
(
response
)
{
try
{
response
=
JSON
.
parse
(
response
);
response
.
_id
=
priv
.
indices
[
index
].
id
;
response
.
_attachment
=
priv
.
indices
[
index
].
attachment
||
"
body
"
;
response_list
[
index
]
=
new
JSONIndex
(
response
);
}
catch
(
e
)
{
return
that
.
error
(
generateErrorObject
(
e
.
message
,
"
Repair is necessary
"
,
"
corrupt
"
));
}
count
+=
1
;
if
(
count
===
priv
.
indices
.
length
)
{
callback
(
response_list
);
}
};
};
for
(
i
=
0
;
i
<
priv
.
indices
.
length
;
i
+=
1
)
{
that
.
addJob
(
"
getAttachment
"
,
priv
.
indices
[
i
].
sub_storage
||
priv
.
sub_storage
,
{
"
_id
"
:
priv
.
indices
[
i
].
id
,
"
_attachment
"
:
priv
.
indices
[
i
].
attachment
||
"
body
"
},
option
,
callbacks
.
success
(
i
),
callbacks
.
error
(
i
)
);
}
};
/**
* Saves all the databases to the remote(s).
*
* @method storeIndexDatabaseList
* @param {Array} database_list The database list
* @param {Object} option The command option
* @param {Function} callback The result callback(err, response)
*/
priv
.
storeIndexDatabaseList
=
function
(
database_list
,
option
,
callback
)
{
var
i
,
count
=
0
,
count_max
=
0
;
function
onAttachmentResponse
(
response
)
{
count
+=
1
;
if
(
count
===
count_max
)
{
callback
({
"
ok
"
:
true
});
}
}
function
onAttachmentError
(
err
)
{
err
.
message
=
"
Unable to store index database.
"
;
that
.
error
(
err
);
}
function
putAttachment
(
i
)
{
that
.
addJob
(
"
putAttachment
"
,
priv
.
indices
[
i
].
sub_storage
||
priv
.
sub_storage
,
{
"
_id
"
:
database_list
[
i
].
_id
,
"
_attachment
"
:
database_list
[
i
].
_attachment
,
"
_data
"
:
JSON
.
stringify
(
database_list
[
i
].
serialized
()),
"
_mimetype
"
:
"
application/json
"
},
option
,
onAttachmentResponse
,
onAttachmentError
);
}
function
post
(
i
)
{
var
doc
=
priv
.
indices
[
i
].
metadata
||
{};
doc
.
_id
=
database_list
[
i
].
_id
;
that
.
addJob
(
"
post
"
,
// with id
priv
.
indices
[
i
].
sub_storage
||
priv
.
sub_storage
,
doc
,
option
,
function
(
response
)
{
putAttachment
(
i
);
},
function
(
err
)
{
if
(
err
.
status
===
409
)
{
return
putAttachment
(
i
);
}
err
.
message
=
"
Unable to store index database.
"
;
that
.
error
(
err
);
}
);
}
for
(
i
=
0
;
i
<
priv
.
indices
.
length
;
i
+=
1
)
{
if
(
database_list
[
i
]
!==
undefined
)
{
count_max
+=
1
;
post
(
i
);
}
}
};
/**
* A generic request method which delegates the request to the sub storage.
* On response, it will index the document from the request and update all
* the databases.
*
* @method genericRequest
* @param {Command} command The JIO command
* @param {Function} method The request method
*/
priv
.
genericRequest
=
function
(
command
,
method
)
{
var
doc
=
command
.
cloneDoc
(),
option
=
command
.
cloneOption
();
that
.
addJob
(
method
,
priv
.
sub_storage
,
doc
,
option
,
function
(
response
)
{
switch
(
method
)
{
case
"
post
"
:
case
"
put
"
:
case
"
remove
"
:
doc
.
_id
=
response
.
id
;
priv
.
getIndexDatabaseList
(
option
,
function
(
database_list
)
{
var
i
;
switch
(
method
)
{
case
"
post
"
:
case
"
put
"
:
for
(
i
=
0
;
i
<
database_list
.
length
;
i
+=
1
)
{
database_list
[
i
].
put
(
doc
);
}
break
;
case
"
remove
"
:
for
(
i
=
0
;
i
<
database_list
.
length
;
i
+=
1
)
{
database_list
[
i
].
remove
(
doc
);
}
break
;
default
:
break
;
}
priv
.
storeIndexDatabaseList
(
database_list
,
option
,
function
()
{
that
.
success
({
"
ok
"
:
true
,
"
id
"
:
doc
.
_id
});
});
});
break
;
default
:
that
.
success
(
response
);
break
;
}
},
function
(
err
)
{
return
that
.
error
(
err
);
}
);
};
/**
* Post the document metadata and update the index
* @method post
* @param {object} command The JIO command
*/
that
.
post
=
function
(
command
)
{
priv
.
genericRequest
(
command
,
'
post
'
);
};
/**
* Update the document metadata and update the index
* @method put
* @param {object} command The JIO command
*/
that
.
put
=
function
(
command
)
{
priv
.
genericRequest
(
command
,
'
put
'
);
};
/**
* Add an attachment to a document (no index modification)
* @method putAttachment
* @param {object} command The JIO command
*/
that
.
putAttachment
=
function
(
command
)
{
priv
.
genericRequest
(
command
,
'
putAttachment
'
);
};
/**
* Get the document metadata
* @method get
* @param {object} command The JIO command
*/
that
.
get
=
function
(
command
)
{
priv
.
genericRequest
(
command
,
'
get
'
);
};
/**
* Get the attachment.
* @method getAttachment
* @param {object} command The JIO command
*/
that
.
getAttachment
=
function
(
command
)
{
priv
.
genericRequest
(
command
,
'
getAttachment
'
);
};
/**
* Remove document - removing documents updates index!.
* @method remove
* @param {object} command The JIO command
*/
that
.
remove
=
function
(
command
)
{
priv
.
genericRequest
(
command
,
'
remove
'
);
};
/**
* Remove attachment
* @method removeAttachment
* @param {object} command The JIO command
*/
that
.
removeAttachment
=
function
(
command
)
{
priv
.
genericRequest
(
command
,
'
removeAttachment
'
);
};
/**
* Gets a document list from the substorage
* Options:
* - {boolean} include_docs Also retrieve the actual document content.
* @method allDocs
* @param {object} command The JIO command
*/
that
.
allDocs
=
function
(
command
)
{
var
option
=
command
.
cloneOption
(),
index
=
priv
.
selectIndex
(
option
.
select_list
||
[]);
// Include docs option is ignored, if you want to get all the document,
// don't use index storage!
option
.
select_list
=
option
.
select_list
||
[];
option
.
select_list
.
push
(
"
_id
"
);
priv
.
getIndexDatabase
(
option
,
index
,
function
(
db
)
{
var
i
,
id
;
db
=
db
.
_database
;
complex_queries
.
QueryFactory
.
create
(
option
.
query
||
''
).
exec
(
db
,
option
);
for
(
i
=
0
;
i
<
db
.
length
;
i
+=
1
)
{
id
=
db
[
i
].
_id
;
delete
db
[
i
].
_id
;
db
[
i
]
=
{
"
id
"
:
id
,
"
key
"
:
id
,
"
value
"
:
db
[
i
],
};
}
that
.
success
({
"
total_rows
"
:
db
.
length
,
"
rows
"
:
db
});
});
};
that
.
check
=
function
(
command
)
{
that
.
repair
(
command
,
true
);
};
priv
.
repairIndexDatabase
=
function
(
command
,
index
,
just_check
)
{
var
i
,
option
=
command
.
cloneOption
();
that
.
addJob
(
'
allDocs
'
,
priv
.
sub_storage
,
{},
{
'
include_docs
'
:
true
},
function
(
response
)
{
var
db_list
=
[],
db
=
new
JSONIndex
({
"
_id
"
:
command
.
getDocId
(),
"
_attachment
"
:
priv
.
indices
[
index
].
attachment
||
"
body
"
,
"
indexing
"
:
priv
.
indices
[
index
].
index
});
for
(
i
=
0
;
i
<
response
.
rows
.
length
;
i
+=
1
)
{
db
.
put
(
response
.
rows
[
i
].
doc
);
}
db_list
[
index
]
=
db
;
if
(
just_check
)
{
priv
.
getIndexDatabase
(
option
,
index
,
function
(
current_db
)
{
if
(
db
.
equals
(
current_db
))
{
return
that
.
success
({
"
ok
"
:
true
,
"
id
"
:
command
.
getDocId
()});
}
return
that
.
error
(
generateErrorObject
(
"
Different Index
"
,
"
Check failed
"
,
"
corrupt index database
"
));
});
}
else
{
priv
.
storeIndexDatabaseList
(
db_list
,
{},
function
()
{
that
.
success
({
"
ok
"
:
true
,
"
id
"
:
command
.
getDocId
()});
});
}
},
function
(
err
)
{
err
.
message
=
"
Unable to repair the index database
"
;
that
.
error
(
err
);
}
);
};
priv
.
repairDocument
=
function
(
command
,
just_check
)
{
var
i
,
option
=
command
.
cloneOption
();
that
.
addJob
(
"
get
"
,
priv
.
sub_storage
,
command
.
cloneDoc
(),
{},
function
(
response
)
{
response
.
_id
=
command
.
getDocId
();
priv
.
getIndexDatabaseList
(
option
,
function
(
database_list
)
{
if
(
just_check
)
{
for
(
i
=
0
;
i
<
database_list
.
length
;
i
+=
1
)
{
try
{
database_list
[
i
].
checkDocument
(
response
);
}
catch
(
e
)
{
return
that
.
error
(
generateErrorObject
(
e
.
message
,
"
Check failed
"
,
"
corrupt index database
"
));
}
}
that
.
success
({
"
_id
"
:
command
.
getDocId
(),
"
ok
"
:
true
});
}
else
{
for
(
i
=
0
;
i
<
database_list
.
length
;
i
+=
1
)
{
database_list
[
i
].
put
(
response
);
}
priv
.
storeIndexDatabaseList
(
database_list
,
option
,
function
()
{
that
.
success
({
"
ok
"
:
true
,
"
id
"
:
command
.
getDocId
()});
});
}
});
},
function
(
err
)
{
err
.
message
=
"
Unable to repair document
"
;
return
that
.
error
(
err
);
}
);
};
that
.
repair
=
function
(
command
,
just_check
)
{
var
database_index
=
-
1
,
i
;
for
(
i
=
0
;
i
<
priv
.
indices
.
length
;
i
+=
1
)
{
if
(
priv
.
indices
[
i
].
id
===
command
.
getDocId
())
{
database_index
=
i
;
break
;
}
}
that
.
addJob
(
"
repair
"
,
priv
.
sub_storage
,
command
.
cloneDoc
(),
command
.
cloneOption
(),
function
(
response
)
{
if
(
database_index
!==
-
1
)
{
priv
.
repairIndexDatabase
(
command
,
database_index
,
just_check
);
}
else
{
priv
.
repairDocument
(
command
,
just_check
);
}
},
function
(
err
)
{
err
.
message
=
"
Could not repair sub storage
"
;
that
.
error
(
err
);
}
);
};
return
that
;
}
jIO
.
addStorageType
(
"
indexed
"
,
indexStorage
);
}));
src/jio.storage/ramstorage.js
deleted
100644 → 0
View file @
3356688e
/*
* Copyright 2013, Nexedi SA
* Released under the LGPL license.
* http://www.gnu.org/licenses/lgpl.html
*/
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global define, jIO, setTimeout, complex_queries */
// define([module_name], [dependencies], module);
(
function
(
dependencies
,
module
)
{
"
use strict
"
;
if
(
typeof
define
===
'
function
'
&&
define
.
amd
)
{
return
define
(
dependencies
,
module
);
}
module
(
jIO
);
}([
'
jio
'
],
function
(
jIO
)
{
var
storage
=
{};
/**
* Returns 4 hexadecimal random characters.
*
* @return {String} The characters
*/
function
S4
()
{
return
(
'
0000
'
+
Math
.
floor
(
Math
.
random
()
*
0x10000
/* 65536 */
).
toString
(
16
)).
slice
(
-
4
);
}
/**
* An Universal Unique ID generator
*
* @return {String} The new UUID.
*/
function
generateUuid
()
{
return
S4
()
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
S4
()
+
S4
();
}
/**
* Checks if an object has no enumerable keys
*
* @param {Object} obj The object
* @return {Boolean} true if empty, else false
*/
function
objectIsEmpty
(
obj
)
{
var
k
;
for
(
k
in
obj
)
{
if
(
obj
.
hasOwnProperty
(
k
))
{
return
false
;
}
}
return
true
;
}
/**
* JIO Ram Storage. Type = 'ram'.
* Memory "database" storage.
*
* Storage Description:
*
* {
* "type": "ram",
* "namespace": <string>, // default 'default'
* }
*
* Document are stored in path
* 'namespace/document_id' like this:
*
* {
* "_id": "document_id",
* "_attachments": {
* "attachment_name": {
* "length": data_length,
* "digest": "md5-XXX",
* "content_type": "mime/type"
* },
* "attachment_name2": {..}, ...
* },
* "metadata_name": "metadata_value"
* "metadata_name2": ...
* ...
* }
*
* Only "_id" and "_attachments" are specific metadata keys, other one can be
* added without loss.
*
* @class RamStorage
*/
function
ramStorage
(
spec
,
my
)
{
var
that
,
priv
=
{},
ramstorage
;
that
=
my
.
basicStorage
(
spec
,
my
);
/*
* Wrapper for the localStorage used to simplify instion of any kind of
* values
*/
ramstorage
=
{
getItem
:
function
(
item
)
{
var
value
=
storage
[
item
];
return
value
===
undefined
?
null
:
JSON
.
parse
(
value
);
},
setItem
:
function
(
item
,
value
)
{
storage
[
item
]
=
JSON
.
stringify
(
value
);
},
removeItem
:
function
(
item
)
{
delete
storage
[
item
];
}
};
// attributes
if
(
typeof
spec
.
namespace
!==
'
string
'
)
{
priv
.
namespace
=
'
default
'
;
}
else
{
priv
.
namespace
=
spec
.
namespace
;
}
// ===================== overrides ======================
that
.
specToStore
=
function
()
{
return
{
"
namespace
"
:
priv
.
namespace
};
};
that
.
validateState
=
function
()
{
return
''
;
};
// ==================== commands ====================
/**
* Create a document in local storage.
* @method post
* @param {object} command The JIO command
*/
that
.
post
=
function
(
command
)
{
setTimeout
(
function
()
{
var
doc
,
doc_id
=
command
.
getDocId
();
if
(
!
doc_id
)
{
doc_id
=
generateUuid
();
}
doc
=
ramstorage
.
getItem
(
priv
.
namespace
+
"
/
"
+
doc_id
);
if
(
doc
===
null
)
{
// the document does not exist
doc
=
command
.
cloneDoc
();
doc
.
_id
=
doc_id
;
delete
doc
.
_attachments
;
ramstorage
.
setItem
(
priv
.
namespace
+
"
/
"
+
doc_id
,
doc
);
that
.
success
({
"
ok
"
:
true
,
"
id
"
:
doc_id
});
}
else
{
// the document already exists
that
.
error
({
"
status
"
:
409
,
"
statusText
"
:
"
Conflicts
"
,
"
error
"
:
"
conflicts
"
,
"
message
"
:
"
Cannot create a new document
"
,
"
reason
"
:
"
Document already exists
"
});
}
});
};
/**
* Create or update a document in local storage.
* @method put
* @param {object} command The JIO command
*/
that
.
put
=
function
(
command
)
{
setTimeout
(
function
()
{
var
doc
,
tmp
;
doc
=
ramstorage
.
getItem
(
priv
.
namespace
+
"
/
"
+
command
.
getDocId
());
if
(
doc
===
null
)
{
// the document does not exist
doc
=
command
.
cloneDoc
();
delete
doc
.
_attachments
;
}
else
{
// the document already exists
tmp
=
command
.
cloneDoc
();
tmp
.
_attachments
=
doc
.
_attachments
;
doc
=
tmp
;
}
// write
ramstorage
.
setItem
(
priv
.
namespace
+
"
/
"
+
command
.
getDocId
(),
doc
);
that
.
success
({
"
ok
"
:
true
,
"
id
"
:
command
.
getDocId
()
});
});
};
/**
* Add an attachment to a document
* @method putAttachment
* @param {object} command The JIO command
*/
that
.
putAttachment
=
function
(
command
)
{
setTimeout
(
function
()
{
var
doc
;
doc
=
ramstorage
.
getItem
(
priv
.
namespace
+
"
/
"
+
command
.
getDocId
());
if
(
doc
===
null
)
{
// the document does not exist
that
.
error
({
"
status
"
:
404
,
"
statusText
"
:
"
Not Found
"
,
"
error
"
:
"
not_found
"
,
"
message
"
:
"
Impossible to add attachment
"
,
"
reason
"
:
"
Document not found
"
});
return
;
}
// the document already exists
doc
.
_attachments
=
doc
.
_attachments
||
{};
doc
.
_attachments
[
command
.
getAttachmentId
()]
=
{
"
content_type
"
:
command
.
getAttachmentMimeType
(),
"
digest
"
:
"
md5-
"
+
command
.
md5SumAttachmentData
(),
"
length
"
:
command
.
getAttachmentLength
()
};
// upload data
ramstorage
.
setItem
(
priv
.
namespace
+
"
/
"
+
command
.
getDocId
()
+
"
/
"
+
command
.
getAttachmentId
(),
command
.
getAttachmentData
());
// write document
ramstorage
.
setItem
(
priv
.
namespace
+
"
/
"
+
command
.
getDocId
(),
doc
);
that
.
success
({
"
ok
"
:
true
,
"
id
"
:
command
.
getDocId
(),
"
attachment
"
:
command
.
getAttachmentId
()
});
});
};
/**
* Get a document
* @method get
* @param {object} command The JIO command
*/
that
.
get
=
function
(
command
)
{
setTimeout
(
function
()
{
var
doc
=
ramstorage
.
getItem
(
priv
.
namespace
+
"
/
"
+
command
.
getDocId
());
if
(
doc
!==
null
)
{
that
.
success
(
doc
);
}
else
{
that
.
error
({
"
status
"
:
404
,
"
statusText
"
:
"
Not Found
"
,
"
error
"
:
"
not_found
"
,
"
message
"
:
"
Cannot find the document
"
,
"
reason
"
:
"
Document does not exist
"
});
}
});
};
/**
* Get a attachment
* @method getAttachment
* @param {object} command The JIO command
*/
that
.
getAttachment
=
function
(
command
)
{
setTimeout
(
function
()
{
var
doc
=
ramstorage
.
getItem
(
priv
.
namespace
+
"
/
"
+
command
.
getDocId
()
+
"
/
"
+
command
.
getAttachmentId
());
if
(
doc
!==
null
)
{
that
.
success
(
doc
);
}
else
{
that
.
error
({
"
status
"
:
404
,
"
statusText
"
:
"
Not Found
"
,
"
error
"
:
"
not_found
"
,
"
message
"
:
"
Cannot find the attachment
"
,
"
reason
"
:
"
Attachment does not exist
"
});
}
});
};
/**
* Remove a document
* @method remove
* @param {object} command The JIO command
*/
that
.
remove
=
function
(
command
)
{
setTimeout
(
function
()
{
var
doc
,
i
,
attachment_list
;
doc
=
ramstorage
.
getItem
(
priv
.
namespace
+
"
/
"
+
command
.
getDocId
());
attachment_list
=
[];
if
(
doc
!==
null
&&
typeof
doc
===
"
object
"
)
{
if
(
typeof
doc
.
_attachments
===
"
object
"
)
{
// prepare list of attachments
for
(
i
in
doc
.
_attachments
)
{
if
(
doc
.
_attachments
.
hasOwnProperty
(
i
))
{
attachment_list
.
push
(
i
);
}
}
}
}
else
{
return
that
.
error
({
"
status
"
:
404
,
"
statusText
"
:
"
Not Found
"
,
"
error
"
:
"
not_found
"
,
"
message
"
:
"
Document not found
"
,
"
reason
"
:
"
missing
"
});
}
ramstorage
.
removeItem
(
priv
.
namespace
+
"
/
"
+
command
.
getDocId
());
// delete all attachments
for
(
i
=
0
;
i
<
attachment_list
.
length
;
i
+=
1
)
{
ramstorage
.
removeItem
(
priv
.
namespace
+
"
/
"
+
command
.
getDocId
()
+
"
/
"
+
attachment_list
[
i
]);
}
that
.
success
({
"
ok
"
:
true
,
"
id
"
:
command
.
getDocId
()
});
});
};
/**
* Remove an attachment
* @method removeAttachment
* @param {object} command The JIO command
*/
that
.
removeAttachment
=
function
(
command
)
{
setTimeout
(
function
()
{
var
doc
,
error
,
i
,
attachment_list
;
error
=
function
(
word
)
{
that
.
error
({
"
status
"
:
404
,
"
statusText
"
:
"
Not Found
"
,
"
error
"
:
"
not_found
"
,
"
message
"
:
word
+
"
not found
"
,
"
reason
"
:
"
missing
"
});
};
doc
=
ramstorage
.
getItem
(
priv
.
namespace
+
"
/
"
+
command
.
getDocId
());
// remove attachment from document
if
(
doc
!==
null
&&
typeof
doc
===
"
object
"
&&
typeof
doc
.
_attachments
===
"
object
"
)
{
if
(
typeof
doc
.
_attachments
[
command
.
getAttachmentId
()]
===
"
object
"
)
{
delete
doc
.
_attachments
[
command
.
getAttachmentId
()];
if
(
priv
.
objectIsEmpty
(
doc
.
_attachments
))
{
delete
doc
.
_attachments
;
}
ramstorage
.
setItem
(
priv
.
namespace
+
"
/
"
+
command
.
getDocId
(),
doc
);
ramstorage
.
removeItem
(
priv
.
namespace
+
"
/
"
+
command
.
getDocId
()
+
"
/
"
+
command
.
getAttachmentId
());
that
.
success
({
"
ok
"
:
true
,
"
id
"
:
command
.
getDocId
(),
"
attachment
"
:
command
.
getAttachmentId
()
});
}
else
{
error
(
"
Attachment
"
);
}
}
else
{
error
(
"
Document
"
);
}
});
};
/**
* Get all filenames belonging to a user from the document index
* @method allDocs
* @param {object} command The JIO command
*/
that
.
allDocs
=
function
(
command
)
{
var
i
,
row
,
path_re
,
rows
=
[],
document_list
,
option
,
document_object
;
document_list
=
[];
path_re
=
new
RegExp
(
"
^
"
+
complex_queries
.
stringEscapeRegexpCharacters
(
priv
.
namespace
)
+
"
/[^/]+$
"
);
option
=
command
.
cloneOption
();
if
(
typeof
complex_queries
!==
"
object
"
||
(
option
.
query
===
undefined
&&
option
.
sort_on
===
undefined
&&
option
.
select_list
===
undefined
&&
option
.
include_docs
===
undefined
))
{
rows
=
[];
for
(
i
in
storage
)
{
if
(
storage
.
hasOwnProperty
(
i
))
{
// filter non-documents
if
(
path_re
.
test
(
i
))
{
row
=
{
"
value
"
:
{}};
row
.
id
=
i
.
split
(
'
/
'
).
slice
(
-
1
)[
0
];
row
.
key
=
row
.
id
;
if
(
command
.
getOption
(
'
include_docs
'
))
{
row
.
doc
=
ramstorage
.
getItem
(
i
);
}
rows
.
push
(
row
);
}
}
}
that
.
success
({
"
rows
"
:
rows
,
"
total_rows
"
:
rows
.
length
});
}
else
{
// create complex query object from returned results
for
(
i
in
storage
)
{
if
(
storage
.
hasOwnProperty
(
i
))
{
if
(
path_re
.
test
(
i
))
{
document_list
.
push
(
ramstorage
.
getItem
(
i
));
}
}
}
option
.
select_list
=
option
.
select_list
||
[];
option
.
select_list
.
push
(
"
_id
"
);
if
(
option
.
include_docs
===
true
)
{
document_object
=
{};
document_list
.
forEach
(
function
(
meta
)
{
document_object
[
meta
.
_id
]
=
meta
;
});
}
complex_queries
.
QueryFactory
.
create
(
option
.
query
||
""
).
exec
(
document_list
,
option
);
document_list
=
document_list
.
map
(
function
(
value
)
{
var
o
=
{
"
id
"
:
value
.
_id
,
"
key
"
:
value
.
_id
};
if
(
option
.
include_docs
===
true
)
{
o
.
doc
=
document_object
[
value
.
_id
];
delete
document_object
[
value
.
_id
];
}
delete
value
.
_id
;
o
.
value
=
value
;
return
o
;
});
that
.
success
({
"
total_rows
"
:
document_list
.
length
,
"
rows
"
:
document_list
});
}
};
return
that
;
}
jIO
.
addStorageType
(
'
ram
'
,
ramStorage
);
}));
src/jio.storage/replicaterevisionstorage.js
deleted
100644 → 0
View file @
3356688e
/*jslint indent: 2, maxlen: 80, nomen: true */
/*global jIO, define */
/**
* JIO Replicate Revision Storage.
* It manages storages that manage revisions and conflicts.
* Description:
* {
* "type": "replicaterevision",
* "storage_list": [
* <sub storage description>,
* ...
* ]
* }
*/
// define([module_name], [dependencies], module);
(
function
(
dependencies
,
module
)
{
"
use strict
"
;
if
(
typeof
define
===
'
function
'
&&
define
.
amd
)
{
return
define
(
dependencies
,
module
);
}
module
(
jIO
);
}([
'
jio
'
],
function
(
jIO
)
{
"
use strict
"
;
jIO
.
addStorageType
(
'
replicaterevision
'
,
function
(
spec
,
my
)
{
var
that
,
priv
=
{};
spec
=
spec
||
{};
that
=
my
.
basicStorage
(
spec
,
my
);
priv
.
storage_list_key
=
"
storage_list
"
;
priv
.
storage_list
=
spec
[
priv
.
storage_list_key
];
priv
.
emptyFunction
=
function
()
{};
that
.
specToStore
=
function
()
{
var
o
=
{};
o
[
priv
.
storage_list_key
]
=
priv
.
storage_list
;
return
o
;
};
/**
* Generate a new uuid
* @method generateUuid
* @return {string} The new uuid
*/
priv
.
generateUuid
=
function
()
{
var
S4
=
function
()
{
var
i
,
string
=
Math
.
floor
(
Math
.
random
()
*
0x10000
/* 65536 */
).
toString
(
16
);
for
(
i
=
string
.
length
;
i
<
4
;
i
+=
1
)
{
string
=
"
0
"
+
string
;
}
return
string
;
};
return
S4
()
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
S4
()
+
S4
();
};
/**
* Create an array containing dictionnary keys
* @method dictKeys2Array
* @param {object} dict The object to convert
* @return {array} The array of keys
*/
priv
.
dictKeys2Array
=
function
(
dict
)
{
var
k
,
newlist
=
[];
for
(
k
in
dict
)
{
if
(
dict
.
hasOwnProperty
(
k
))
{
newlist
.
push
(
k
);
}
}
return
newlist
;
};
/**
* Checks a revision format
* @method checkRevisionFormat
* @param {string} revision The revision string
* @return {boolean} True if ok, else false
*/
priv
.
checkRevisionFormat
=
function
(
revision
)
{
return
(
/^
[
0-9
]
+-
[
0-9a-zA-Z_
]
+$/
.
test
(
revision
));
};
/**
* Clones an object in deep (without functions)
* @method clone
* @param {any} object The object to clone
* @return {any} The cloned object
*/
priv
.
clone
=
function
(
object
)
{
var
tmp
=
JSON
.
stringify
(
object
);
if
(
tmp
===
undefined
)
{
return
undefined
;
}
return
JSON
.
parse
(
tmp
);
};
/**
* Like addJob but also return the method and the index of the storage
* @method send
* @param {string} method The request method
* @param {number} index The storage index
* @param {object} doc The document object
* @param {object} option The request object
* @param {function} callback The callback. Parameters:
* - {string} The request method
* - {number} The storage index
* - {object} The error object
* - {object} The response object
*/
priv
.
send
=
function
(
method
,
index
,
doc
,
option
,
callback
)
{
var
wrapped_callback_success
,
wrapped_callback_error
;
callback
=
callback
||
priv
.
emptyFunction
;
wrapped_callback_success
=
function
(
response
)
{
callback
(
method
,
index
,
undefined
,
response
);
};
wrapped_callback_error
=
function
(
err
)
{
callback
(
method
,
index
,
err
,
undefined
);
};
that
.
addJob
(
method
,
priv
.
storage_list
[
index
],
doc
,
option
,
wrapped_callback_success
,
wrapped_callback_error
);
};
/**
* Use "send" method to all sub storages.
* Calling "callback" for each storage response.
* @method sendToAll
* @param {string} method The request method
* @param {object} doc The document object
* @param {object} option The request option
* @param {function} callback The callback. Parameters:
* - {string} The request method
* - {number} The storage index
* - {object} The error object
* - {object} The response object
*/
priv
.
sendToAll
=
function
(
method
,
doc
,
option
,
callback
)
{
var
i
;
for
(
i
=
0
;
i
<
priv
.
storage_list
.
length
;
i
+=
1
)
{
priv
.
send
(
method
,
i
,
doc
,
option
,
callback
);
}
};
/**
* Use "send" method to all sub storages.
* Calling "callback" only with the first response
* @method sendToAllFastestResponseOnly
* @param {string} method The request method
* @param {object} doc The document object
* @param {object} option The request option
* @param {function} callback The callback. Parameters:
* - {string} The request method
* - {object} The error object
* - {object} The response object
*/
priv
.
sendToAllFastestResponseOnly
=
function
(
method
,
doc
,
option
,
callback
)
{
var
i
,
callbackWrapper
,
error_count
,
last_error
;
error_count
=
0
;
callbackWrapper
=
function
(
method
,
index
,
err
,
response
)
{
if
(
err
)
{
error_count
+=
1
;
last_error
=
err
;
if
(
error_count
===
priv
.
storage_list
.
length
)
{
return
callback
(
method
,
err
,
response
);
}
}
callback
(
method
,
err
,
response
);
};
for
(
i
=
0
;
i
<
priv
.
storage_list
.
length
;
i
+=
1
)
{
priv
.
send
(
method
,
i
,
doc
,
option
,
callbackWrapper
);
}
};
/**
* Use "sendToAll" method, calling "callback" at the last response with
* the response list
* @method sendToAllGetResponseList
* @param {string} method The request method
* @param {object} doc The document object
* @param {object} option The request option
* @return {function} callback The callback. Parameters:
* - {string} The request method
* - {object} The error object
* - {object} The response object
*/
priv
.
sendToAllGetResponseList
=
function
(
method
,
doc
,
option
,
callback
)
{
var
wrapper
,
callback_count
=
0
,
response_list
=
[],
error_list
=
[];
response_list
.
length
=
priv
.
storage_list
.
length
;
wrapper
=
function
(
method
,
index
,
err
,
response
)
{
error_list
[
index
]
=
err
;
response_list
[
index
]
=
response
;
callback_count
+=
1
;
if
(
callback_count
===
priv
.
storage_list
.
length
)
{
callback
(
error_list
,
response_list
);
}
};
priv
.
sendToAll
(
method
,
doc
,
option
,
wrapper
);
};
/**
* Checks if the sub storage are identical
* @method check
* @param {object} command The JIO command
*/
that
.
check
=
function
(
command
)
{
function
callback
(
err
,
response
)
{
if
(
err
)
{
return
that
.
error
(
err
);
}
that
.
success
(
response
);
}
priv
.
check
(
command
.
cloneDoc
(),
command
.
cloneOption
(),
callback
);
};
/**
* Repair the sub storages to make them identical
* @method repair
* @param {object} command The JIO command
*/
that
.
repair
=
function
(
command
)
{
function
callback
(
err
,
response
)
{
if
(
err
)
{
return
that
.
error
(
err
);
}
that
.
success
(
response
);
}
priv
.
repair
(
command
.
cloneDoc
(),
command
.
cloneOption
(),
true
,
callback
);
};
priv
.
check
=
function
(
doc
,
option
,
success
,
error
)
{
priv
.
repair
(
doc
,
option
,
false
,
success
,
error
);
};
priv
.
repair
=
function
(
doc
,
option
,
repair
,
callback
)
{
var
functions
=
{};
callback
=
callback
||
priv
.
emptyFunction
;
option
=
option
||
{};
functions
.
begin
=
function
()
{
// };
// functions.repairAllSubStorages = function () {
var
i
;
for
(
i
=
0
;
i
<
priv
.
storage_list
.
length
;
i
+=
1
)
{
priv
.
send
(
repair
?
"
repair
"
:
"
check
"
,
i
,
doc
,
option
,
functions
.
repairAllSubStoragesCallback
);
}
};
functions
.
repair_sub_storages_count
=
0
;
functions
.
repairAllSubStoragesCallback
=
function
(
method
,
index
,
err
,
response
)
{
if
(
err
)
{
return
that
.
error
(
err
);
}
functions
.
repair_sub_storages_count
+=
1
;
if
(
functions
.
repair_sub_storages_count
===
priv
.
storage_list
.
length
)
{
functions
.
getAllDocuments
(
functions
.
newParam
(
doc
,
option
,
repair
));
}
};
functions
.
newParam
=
function
(
doc
,
option
,
repair
)
{
var
param
=
{
"
doc
"
:
doc
,
// the document to repair
"
option
"
:
option
,
"
repair
"
:
repair
,
"
responses
"
:
{
"
count
"
:
0
,
"
list
"
:
[
// 0: response0
// 1: response1
// 2: response2
],
"
stats
"
:
{
// responseA: [0, 1]
// responseB: [2]
},
"
stats_items
"
:
[
// 0: [responseA, [0, 1]]
// 1: [responseB, [2]]
],
"
attachments
"
:
{
// attachmentA : {_id: attachmentA, _revs_info, _mimetype: ..}
// attachmentB : {_id: attachmentB, _revs_info, _mimetype: ..}
}
},
"
conflicts
"
:
{
// revC: true
// revD: true
},
"
deal_result_state
"
:
"
ok
"
,
"
my_rev
"
:
undefined
};
param
.
responses
.
list
.
length
=
priv
.
storage_list
.
length
;
return
param
;
};
functions
.
getAllDocuments
=
function
(
param
)
{
var
i
,
doc
=
priv
.
clone
(
param
.
doc
),
option
=
priv
.
clone
(
param
.
option
);
option
.
conflicts
=
true
;
option
.
revs
=
true
;
option
.
revs_info
=
true
;
for
(
i
=
0
;
i
<
priv
.
storage_list
.
length
;
i
+=
1
)
{
// if the document is not loaded
priv
.
send
(
"
get
"
,
i
,
doc
,
option
,
functions
.
dealResults
(
param
));
}
functions
.
finished_count
+=
1
;
};
functions
.
dealResults
=
function
(
param
)
{
return
function
(
method
,
index
,
err
,
response
)
{
var
response_object
=
{};
if
(
param
.
deal_result_state
!==
"
ok
"
)
{
// deal result is in a wrong state, exit
return
;
}
if
(
err
)
{
if
(
err
.
status
!==
404
)
{
// get document failed, exit
param
.
deal_result_state
=
"
error
"
;
callback
({
"
status
"
:
40
,
"
statusText
"
:
"
Check Failed
"
,
"
error
"
:
"
check_failed
"
,
"
message
"
:
"
An error occured on the sub storage
"
,
"
reason
"
:
err
.
reason
},
undefined
);
return
;
}
}
// success to get the document
// add the response in memory
param
.
responses
.
count
+=
1
;
param
.
responses
.
list
[
index
]
=
response
;
// add the conflicting revision for other synchronizations
functions
.
addConflicts
(
param
,
(
response
||
{}).
_conflicts
);
if
(
param
.
responses
.
count
!==
param
.
responses
.
list
.
length
)
{
// this is not the last response, wait for the next response
return
;
}
// this is now the last response
functions
.
makeResponsesStats
(
param
.
responses
);
if
(
param
.
responses
.
stats_items
.
length
===
1
)
{
// the responses are equals!
response_object
.
ok
=
true
;
response_object
.
id
=
param
.
doc
.
_id
;
if
(
doc
.
_rev
)
{
response_object
.
rev
=
doc
.
_rev
;
// "rev": (typeof param.responses.list[0] === "object" ?
// param.responses.list[0]._rev : undefined)
}
callback
(
undefined
,
response_object
);
return
;
}
// the responses are different
if
(
param
.
repair
===
false
)
{
// do not repair
callback
({
"
status
"
:
41
,
"
statusText
"
:
"
Check Not Ok
"
,
"
error
"
:
"
check_not_ok
"
,
"
message
"
:
"
Some documents are different in the sub storages
"
,
"
reason
"
:
"
Storage contents differ
"
},
undefined
);
return
;
}
// repair
functions
.
getAttachments
(
param
);
};
};
functions
.
addConflicts
=
function
(
param
,
list
)
{
var
i
;
list
=
list
||
[];
for
(
i
=
0
;
i
<
list
.
length
;
i
+=
1
)
{
param
.
conflicts
[
list
[
i
]]
=
true
;
}
};
functions
.
makeResponsesStats
=
function
(
responses
)
{
var
i
,
str_response
;
for
(
i
=
0
;
i
<
responses
.
count
;
i
+=
1
)
{
str_response
=
JSON
.
stringify
(
responses
.
list
[
i
]);
if
(
responses
.
stats
[
str_response
]
===
undefined
)
{
responses
.
stats
[
str_response
]
=
[];
responses
.
stats_items
.
push
([
str_response
,
responses
.
stats
[
str_response
]
]);
}
responses
.
stats
[
str_response
].
push
(
i
);
}
};
functions
.
getAttachments
=
function
(
param
)
{
var
response
,
parsed_response
,
attachment
;
for
(
response
in
param
.
responses
.
stats
)
{
if
(
param
.
responses
.
stats
.
hasOwnProperty
(
response
))
{
parsed_response
=
JSON
.
parse
(
response
);
for
(
attachment
in
parsed_response
.
_attachments
)
{
if
((
parsed_response
.
_attachments
).
hasOwnProperty
(
attachment
))
{
functions
.
get_attachment_count
+=
1
;
priv
.
send
(
"
getAttachment
"
,
param
.
responses
.
stats
[
response
][
0
],
{
"
_id
"
:
param
.
doc
.
_id
,
"
_attachment
"
:
attachment
,
"
_rev
"
:
JSON
.
parse
(
response
).
_rev
},
param
.
option
,
functions
.
getAttachmentsCallback
(
param
,
attachment
,
param
.
responses
.
stats
[
response
]
)
);
}
}
}
}
};
functions
.
get_attachment_count
=
0
;
functions
.
getAttachmentsCallback
=
function
(
param
,
attachment_id
,
index_list
)
{
return
function
(
method
,
index
,
err
,
response
)
{
if
(
err
)
{
callback
({
"
status
"
:
40
,
"
statusText
"
:
"
Check Failed
"
,
"
error
"
:
"
check_failed
"
,
"
message
"
:
"
Unable to retreive attachments
"
,
"
reason
"
:
err
.
reason
},
undefined
);
return
;
}
functions
.
get_attachment_count
-=
1
;
param
.
responses
.
attachments
[
attachment_id
]
=
response
;
if
(
functions
.
get_attachment_count
===
0
)
{
functions
.
synchronizeAllSubStorage
(
param
);
if
(
param
.
option
.
synchronize_conflicts
!==
false
)
{
functions
.
synchronizeConflicts
(
param
);
}
}
};
};
functions
.
synchronizeAllSubStorage
=
function
(
param
)
{
var
i
,
j
,
len
=
param
.
responses
.
stats_items
.
length
;
for
(
i
=
0
;
i
<
len
;
i
+=
1
)
{
// browsing responses
for
(
j
=
0
;
j
<
len
;
j
+=
1
)
{
// browsing storage list
if
(
i
!==
j
)
{
functions
.
synchronizeResponseToSubStorage
(
param
,
param
.
responses
.
stats_items
[
i
][
0
],
param
.
responses
.
stats_items
[
j
][
1
]
);
}
}
}
functions
.
finished_count
-=
1
;
};
functions
.
synchronizeResponseToSubStorage
=
function
(
param
,
response
,
storage_list
)
{
var
i
,
new_doc
,
attachment_to_put
=
[];
if
(
response
===
undefined
)
{
// no response to sync
return
;
}
new_doc
=
JSON
.
parse
(
response
);
new_doc
.
_revs
=
new_doc
.
_revisions
;
delete
new_doc
.
_rev
;
delete
new_doc
.
_revisions
;
delete
new_doc
.
_conflicts
;
for
(
i
in
new_doc
.
_attachments
)
{
if
(
new_doc
.
_attachments
.
hasOwnProperty
(
i
))
{
attachment_to_put
.
push
({
"
_id
"
:
i
,
"
_mimetype
"
:
new_doc
.
_attachments
[
i
].
content_type
,
"
_revs_info
"
:
new_doc
.
_revs_info
});
}
}
for
(
i
=
0
;
i
<
storage_list
.
length
;
i
+=
1
)
{
functions
.
finished_count
+=
attachment_to_put
.
length
||
1
;
priv
.
send
(
"
put
"
,
storage_list
[
i
],
new_doc
,
param
.
option
,
functions
.
putAttachments
(
param
,
attachment_to_put
)
);
}
functions
.
finished_count
+=
1
;
functions
.
finished
();
};
functions
.
synchronizeConflicts
=
function
(
param
)
{
var
rev
,
new_doc
,
new_option
;
new_option
=
priv
.
clone
(
param
.
option
);
new_option
.
synchronize_conflict
=
false
;
for
(
rev
in
param
.
conflicts
)
{
if
(
param
.
conflicts
.
hasOwnProperty
(
rev
))
{
new_doc
=
priv
.
clone
(
param
.
doc
);
new_doc
.
_rev
=
rev
;
// no need to synchronize all the conflicts again, do it once
functions
.
getAllDocuments
(
functions
.
newParam
(
new_doc
,
new_option
,
param
.
repair
));
}
}
};
functions
.
putAttachments
=
function
(
param
,
attachment_to_put
)
{
return
function
(
method
,
index
,
err
,
response
)
{
var
i
,
attachment
;
if
(
err
)
{
return
callback
({
"
status
"
:
40
,
"
statusText
"
:
"
Check Failed
"
,
"
error
"
:
"
check_failed
"
,
"
message
"
:
"
Unable to copy attachments
"
,
"
reason
"
:
err
.
reason
},
undefined
);
}
for
(
i
=
0
;
i
<
attachment_to_put
.
length
;
i
+=
1
)
{
attachment
=
{
"
_id
"
:
param
.
doc
.
_id
,
"
_attachment
"
:
attachment_to_put
[
i
].
_id
,
"
_mimetype
"
:
attachment_to_put
[
i
].
_mimetype
,
"
_revs_info
"
:
attachment_to_put
[
i
].
_revs_info
,
// "_revs_info": param.responses.list[index]._revs_info,
"
_data
"
:
param
.
responses
.
attachments
[
attachment_to_put
[
i
].
_id
]
};
priv
.
send
(
"
putAttachment
"
,
index
,
attachment
,
option
,
functions
.
putAttachmentCallback
(
param
)
);
}
if
(
attachment_to_put
.
length
===
0
)
{
functions
.
finished
();
}
};
};
functions
.
putAttachmentCallback
=
function
(
param
)
{
return
function
(
method
,
index
,
err
,
response
)
{
if
(
err
)
{
return
callback
(
err
,
undefined
);
}
functions
.
finished
();
};
};
functions
.
finished_count
=
0
;
functions
.
finished
=
function
()
{
var
response_object
=
{};
functions
.
finished_count
-=
1
;
if
(
functions
.
finished_count
===
0
)
{
response_object
.
ok
=
true
;
response_object
.
id
=
doc
.
_id
;
if
(
doc
.
_rev
)
{
response_object
.
rev
=
doc
.
_rev
;
}
callback
(
undefined
,
response_object
);
}
};
functions
.
begin
();
};
/**
* The generic method to use
* @method genericRequest
* @param {object} command The JIO command
* @param {string} method The method to use
*/
that
.
genericRequest
=
function
(
command
,
method
)
{
var
doc
=
command
.
cloneDoc
();
doc
.
_id
=
doc
.
_id
||
priv
.
generateUuid
();
priv
.
sendToAllFastestResponseOnly
(
method
,
doc
,
command
.
cloneOption
(),
function
(
method
,
err
,
response
)
{
if
(
err
)
{
return
that
.
error
(
err
);
}
that
.
success
(
response
);
}
);
};
/**
* Post the document metadata to all sub storages
* @method post
* @param {object} command The JIO command
*/
that
.
post
=
function
(
command
)
{
that
.
genericRequest
(
command
,
"
put
"
);
};
/**
* Put the document metadata to all sub storages
* @method put
* @param {object} command The JIO command
*/
that
.
put
=
function
(
command
)
{
that
.
genericRequest
(
command
,
"
post
"
);
};
/**
* Put an attachment to a document to all sub storages
* @method putAttachment
* @param {object} command The JIO command
*/
that
.
putAttachment
=
function
(
command
)
{
that
.
genericRequest
(
command
,
"
putAttachment
"
);
};
/**
* Get the document from all sub storages, get the fastest.
* @method get
* @param {object} command The JIO command
*/
that
.
get
=
function
(
command
)
{
that
.
genericRequest
(
command
,
"
get
"
);
};
/**
* Get the attachment from all sub storages, get the fastest.
* @method getAttachment
* @param {object} command The JIO command
*/
that
.
getAttachment
=
function
(
command
)
{
that
.
genericRequest
(
command
,
"
getAttachment
"
);
};
/**
* Remove the document from all sub storages.
* @method remove
* @param {object} command The JIO command
*/
that
.
remove
=
function
(
command
)
{
that
.
genericRequest
(
command
,
"
remove
"
);
};
/**
* Remove the attachment from all sub storages.
* @method remove
* @param {object} command The JIO command
*/
that
.
removeAttachment
=
function
(
command
)
{
that
.
genericRequest
(
command
,
"
removeAttachment
"
);
};
return
that
;
});
}));
src/jio.storage/replicatestorage.js
deleted
100644 → 0
View file @
3356688e
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global jIO: true */
jIO
.
addStorageType
(
'
replicate
'
,
function
(
spec
,
my
)
{
var
that
,
cloned_option
,
priv
=
{},
super_serialized
=
that
.
serialized
;
spec
=
spec
||
{};
that
=
my
.
basicStorage
(
spec
,
my
);
priv
.
return_value_array
=
[];
priv
.
storagelist
=
spec
.
storagelist
||
[];
priv
.
nb_storage
=
priv
.
storagelist
.
length
;
that
.
serialized
=
function
()
{
var
o
=
super_serialized
();
o
.
storagelist
=
priv
.
storagelist
;
return
o
;
};
that
.
validateState
=
function
()
{
if
(
priv
.
storagelist
.
length
===
0
)
{
return
'
Need at least one parameter: "storagelist"
'
+
'
containing at least one storage.
'
;
}
return
''
;
};
priv
.
isTheLast
=
function
(
error_array
)
{
return
(
error_array
.
length
===
priv
.
nb_storage
);
};
priv
.
doJob
=
function
(
command
,
errormessage
,
nodocid
)
{
var
done
=
false
,
error_array
=
[],
i
,
error
=
function
(
err
)
{
if
(
!
done
)
{
error_array
.
push
(
err
);
if
(
priv
.
isTheLast
(
error_array
))
{
that
.
error
({
status
:
207
,
statusText
:
'
Multi-Status
'
,
error
:
'
multi_status
'
,
message
:
'
All
'
+
errormessage
+
(
!
nodocid
?
'
"
'
+
command
.
getDocId
()
+
'
"
'
:
'
'
)
+
'
requests have failed.
'
,
reason
:
'
requests fail
'
,
array
:
error_array
});
}
}
},
success
=
function
(
val
)
{
if
(
!
done
)
{
done
=
true
;
that
.
success
(
val
);
}
};
for
(
i
=
0
;
i
<
priv
.
nb_storage
;
i
+=
1
)
{
cloned_option
=
command
.
cloneOption
();
that
.
addJob
(
command
.
getLabel
(),
priv
.
storagelist
[
i
],
command
.
cloneDoc
(),
cloned_option
,
success
,
error
);
}
};
that
.
post
=
function
(
command
)
{
priv
.
doJob
(
command
,
'
post
'
);
that
.
end
();
};
/**
* Save a document in several storages.
* @method put
*/
that
.
put
=
function
(
command
)
{
priv
.
doJob
(
command
,
'
put
'
);
that
.
end
();
};
/**
* Load a document from several storages, and send the first retreived
* document.
* @method get
*/
that
.
get
=
function
(
command
)
{
priv
.
doJob
(
command
,
'
get
'
);
that
.
end
();
};
/**
* Get a document list from several storages, and returns the first
* retreived document list.
* @method allDocs
*/
that
.
allDocs
=
function
(
command
)
{
priv
.
doJob
(
command
,
'
allDocs
'
,
true
);
that
.
end
();
};
/**
* Remove a document from several storages.
* @method remove
*/
that
.
remove
=
function
(
command
)
{
priv
.
doJob
(
command
,
'
remove
'
);
that
.
end
();
};
return
that
;
});
src/jio.storage/revisionstorage.js
deleted
100644 → 0
View file @
3356688e
/*jslint indent: 2, maxlen: 80, nomen: true */
/*global jIO, hex_sha256, setTimeout, define */
/**
* JIO Revision Storage.
* It manages document version and can generate conflicts.
* Description:
* {
* "type": "revision",
* "sub_storage": <sub storage description>
* }
*/
// define([module_name], [dependencies], module);
(
function
(
dependencies
,
module
)
{
"
use strict
"
;
if
(
typeof
define
===
'
function
'
&&
define
.
amd
)
{
return
define
(
dependencies
,
module
);
}
module
(
jIO
,
{
hex_sha256
:
hex_sha256
});
}([
'
jio
'
,
'
sha256
'
],
function
(
jIO
,
sha256
)
{
"
use strict
"
;
jIO
.
addStorageType
(
"
revision
"
,
function
(
spec
,
my
)
{
var
that
=
{},
priv
=
{};
spec
=
spec
||
{};
that
=
my
.
basicStorage
(
spec
,
my
);
// ATTRIBUTES //
priv
.
doc_tree_suffix
=
"
.revision_tree.json
"
;
priv
.
sub_storage
=
spec
.
sub_storage
;
// METHODS //
/**
* Description to store in order to be restored later
* @method specToStore
* @return {object} Descriptions to store
*/
that
.
specToStore
=
function
()
{
return
{
"
sub_storage
"
:
priv
.
sub_storage
};
};
/**
* Clones an object in deep (without functions)
* @method clone
* @param {any} object The object to clone
* @return {any} The cloned object
*/
priv
.
clone
=
function
(
object
)
{
var
tmp
=
JSON
.
stringify
(
object
);
if
(
tmp
===
undefined
)
{
return
undefined
;
}
return
JSON
.
parse
(
tmp
);
};
/**
* Generate a new uuid
* @method generateUuid
* @return {string} The new uuid
*/
priv
.
generateUuid
=
function
()
{
var
S4
=
function
()
{
/* 65536 */
var
i
,
string
=
Math
.
floor
(
Math
.
random
()
*
0x10000
).
toString
(
16
);
for
(
i
=
string
.
length
;
i
<
4
;
i
+=
1
)
{
string
=
'
0
'
+
string
;
}
return
string
;
};
return
S4
()
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
S4
()
+
S4
();
};
/**
* Generates a hash code of a string
* @method hashCode
* @param {string} string The string to hash
* @return {string} The string hash code
*/
priv
.
hashCode
=
function
(
string
)
{
return
sha256
.
hex_sha256
(
string
);
};
/**
* Checks a revision format
* @method checkDocumentRevisionFormat
* @param {object} doc The document object
* @return {object} null if ok, else error object
*/
priv
.
checkDocumentRevisionFormat
=
function
(
doc
)
{
var
send_error
=
function
(
message
)
{
return
{
"
status
"
:
31
,
"
statusText
"
:
"
Wrong Revision Format
"
,
"
error
"
:
"
wrong_revision_format
"
,
"
message
"
:
message
,
"
reason
"
:
"
Revision is wrong
"
};
};
if
(
typeof
doc
.
_rev
===
"
string
"
)
{
if
(
/^
[
0-9
]
+-
[
0-9a-zA-Z
]
+$/
.
test
(
doc
.
_rev
)
===
false
)
{
return
send_error
(
"
The document revision does not match
"
+
"
^[0-9]+-[0-9a-zA-Z]+$
"
);
}
}
if
(
typeof
doc
.
_revs
===
"
object
"
)
{
if
(
typeof
doc
.
_revs
.
start
!==
"
number
"
||
typeof
doc
.
_revs
.
ids
!==
"
object
"
||
typeof
doc
.
_revs
.
ids
.
length
!==
"
number
"
)
{
return
send_error
(
"
The document revision history is not well formated
"
);
}
}
if
(
typeof
doc
.
_revs_info
===
"
object
"
)
{
if
(
typeof
doc
.
_revs_info
.
length
!==
"
number
"
)
{
return
send_error
(
"
The document revision information
"
+
"
is not well formated
"
);
}
}
};
/**
* Creates a new document tree
* @method newDocTree
* @return {object} The new document tree
*/
priv
.
newDocTree
=
function
()
{
return
{
"
children
"
:
[]};
};
/**
* Convert revs_info to a simple revisions history
* @method revsInfoToHistory
* @param {array} revs_info The revs info
* @return {object} The revisions history
*/
priv
.
revsInfoToHistory
=
function
(
revs_info
)
{
var
i
,
revisions
=
{
"
start
"
:
0
,
"
ids
"
:
[]
};
revs_info
=
revs_info
||
[];
if
(
revs_info
.
length
>
0
)
{
revisions
.
start
=
parseInt
(
revs_info
[
0
].
rev
.
split
(
'
-
'
)[
0
],
10
);
}
for
(
i
=
0
;
i
<
revs_info
.
length
;
i
+=
1
)
{
revisions
.
ids
.
push
(
revs_info
[
i
].
rev
.
split
(
'
-
'
)[
1
]);
}
return
revisions
;
};
/**
* Convert the revision history object to an array of revisions.
* @method revisionHistoryToList
* @param {object} revs The revision history
* @return {array} The revision array
*/
priv
.
revisionHistoryToList
=
function
(
revs
)
{
var
i
,
start
=
revs
.
start
,
new_list
=
[];
for
(
i
=
0
;
i
<
revs
.
ids
.
length
;
i
+=
1
,
start
-=
1
)
{
new_list
.
push
(
start
+
"
-
"
+
revs
.
ids
[
i
]);
}
return
new_list
;
};
/**
* Convert revision list to revs info.
* @method revisionListToRevsInfo
* @param {array} revision_list The revision list
* @param {object} doc_tree The document tree
* @return {array} The document revs info
*/
priv
.
revisionListToRevsInfo
=
function
(
revision_list
,
doc_tree
)
{
var
revisionListToRevsInfoRec
,
revs_info
=
[],
j
;
for
(
j
=
0
;
j
<
revision_list
.
length
;
j
+=
1
)
{
revs_info
.
push
({
"
rev
"
:
revision_list
[
j
],
"
status
"
:
"
missing
"
});
}
revisionListToRevsInfoRec
=
function
(
index
,
doc_tree
)
{
var
child
,
i
;
if
(
index
<
0
)
{
return
;
}
for
(
i
=
0
;
i
<
doc_tree
.
children
.
length
;
i
+=
1
)
{
child
=
doc_tree
.
children
[
i
];
if
(
child
.
rev
===
revision_list
[
index
])
{
revs_info
[
index
].
status
=
child
.
status
;
revisionListToRevsInfoRec
(
index
-
1
,
child
);
}
}
};
revisionListToRevsInfoRec
(
revision_list
.
length
-
1
,
doc_tree
);
return
revs_info
;
};
/**
* Update a document metadata revision properties
* @method fillDocumentRevisionProperties
* @param {object} doc The document object
* @param {object} doc_tree The document tree
*/
priv
.
fillDocumentRevisionProperties
=
function
(
doc
,
doc_tree
)
{
if
(
doc
.
_revs_info
)
{
doc
.
_revs
=
priv
.
revsInfoToHistory
(
doc
.
_revs_info
);
}
else
if
(
doc
.
_revs
)
{
doc
.
_revs_info
=
priv
.
revisionListToRevsInfo
(
priv
.
revisionHistoryToList
(
doc
.
_revs
),
doc_tree
);
}
else
if
(
doc
.
_rev
)
{
doc
.
_revs_info
=
priv
.
getRevisionInfo
(
doc
.
_rev
,
doc_tree
);
doc
.
_revs
=
priv
.
revsInfoToHistory
(
doc
.
_revs_info
);
}
else
{
doc
.
_revs_info
=
[];
doc
.
_revs
=
{
"
start
"
:
0
,
"
ids
"
:
[]};
}
if
(
doc
.
_revs
.
start
>
0
)
{
doc
.
_rev
=
doc
.
_revs
.
start
+
"
-
"
+
doc
.
_revs
.
ids
[
0
];
}
else
{
delete
doc
.
_rev
;
}
};
/**
* Generates the next revision of a document.
* @methode generateNextRevision
* @param {object} doc The document metadata
* @param {boolean} deleted_flag The deleted flag
* @return {array} 0:The next revision number and 1:the hash code
*/
priv
.
generateNextRevision
=
function
(
doc
,
deleted_flag
)
{
var
string
,
revision_history
,
revs_info
,
pseudo_revision
;
doc
=
priv
.
clone
(
doc
)
||
{};
revision_history
=
doc
.
_revs
;
revs_info
=
doc
.
_revs_info
;
delete
doc
.
_rev
;
delete
doc
.
_revs
;
delete
doc
.
_revs_info
;
string
=
JSON
.
stringify
(
doc
)
+
JSON
.
stringify
(
revision_history
)
+
JSON
.
stringify
(
deleted_flag
?
true
:
false
);
revision_history
.
start
+=
1
;
revision_history
.
ids
.
unshift
(
priv
.
hashCode
(
string
));
doc
.
_revs
=
revision_history
;
doc
.
_rev
=
revision_history
.
start
+
"
-
"
+
revision_history
.
ids
[
0
];
revs_info
.
unshift
({
"
rev
"
:
doc
.
_rev
,
"
status
"
:
deleted_flag
?
"
deleted
"
:
"
available
"
});
doc
.
_revs_info
=
revs_info
;
return
doc
;
};
/**
* Gets the revs info from the document tree
* @method getRevisionInfo
* @param {string} revision The revision to search for
* @param {object} doc_tree The document tree
* @return {array} The revs info
*/
priv
.
getRevisionInfo
=
function
(
revision
,
doc_tree
)
{
var
getRevisionInfoRec
;
getRevisionInfoRec
=
function
(
doc_tree
)
{
var
i
,
child
,
revs_info
;
for
(
i
=
0
;
i
<
doc_tree
.
children
.
length
;
i
+=
1
)
{
child
=
doc_tree
.
children
[
i
];
if
(
child
.
rev
===
revision
)
{
return
[{
"
rev
"
:
child
.
rev
,
"
status
"
:
child
.
status
}];
}
revs_info
=
getRevisionInfoRec
(
child
);
if
(
revs_info
.
length
>
0
||
revision
===
undefined
)
{
revs_info
.
push
({
"
rev
"
:
child
.
rev
,
"
status
"
:
child
.
status
});
return
revs_info
;
}
}
return
[];
};
return
getRevisionInfoRec
(
doc_tree
);
};
priv
.
updateDocumentTree
=
function
(
doc
,
doc_tree
)
{
var
revs_info
,
updateDocumentTreeRec
,
next_rev
;
doc
=
priv
.
clone
(
doc
);
revs_info
=
doc
.
_revs_info
;
updateDocumentTreeRec
=
function
(
doc_tree
,
revs_info
)
{
var
i
,
child
,
info
;
if
(
revs_info
.
length
===
0
)
{
return
;
}
info
=
revs_info
.
pop
();
for
(
i
=
0
;
i
<
doc_tree
.
children
.
length
;
i
+=
1
)
{
child
=
doc_tree
.
children
[
i
];
if
(
child
.
rev
===
info
.
rev
)
{
return
updateDocumentTreeRec
(
child
,
revs_info
);
}
}
doc_tree
.
children
.
unshift
({
"
rev
"
:
info
.
rev
,
"
status
"
:
info
.
status
,
"
children
"
:
[]
});
updateDocumentTreeRec
(
doc_tree
.
children
[
0
],
revs_info
);
};
updateDocumentTreeRec
(
doc_tree
,
priv
.
clone
(
revs_info
));
};
priv
.
send
=
function
(
method
,
doc
,
option
,
callback
)
{
that
.
addJob
(
method
,
priv
.
sub_storage
,
doc
,
option
,
function
(
success
)
{
callback
(
undefined
,
success
);
},
function
(
err
)
{
callback
(
err
,
undefined
);
}
);
};
priv
.
getWinnerRevsInfo
=
function
(
doc_tree
)
{
var
revs_info
=
[],
getWinnerRevsInfoRec
;
getWinnerRevsInfoRec
=
function
(
doc_tree
,
tmp_revs_info
)
{
var
i
;
if
(
doc_tree
.
rev
)
{
tmp_revs_info
.
unshift
({
"
rev
"
:
doc_tree
.
rev
,
"
status
"
:
doc_tree
.
status
});
}
if
(
doc_tree
.
children
.
length
===
0
)
{
if
(
revs_info
.
length
===
0
||
(
revs_info
[
0
].
status
!==
"
available
"
&&
tmp_revs_info
[
0
].
status
===
"
available
"
)
||
(
tmp_revs_info
[
0
].
status
===
"
available
"
&&
revs_info
.
length
<
tmp_revs_info
.
length
))
{
revs_info
=
priv
.
clone
(
tmp_revs_info
);
}
}
for
(
i
=
0
;
i
<
doc_tree
.
children
.
length
;
i
+=
1
)
{
getWinnerRevsInfoRec
(
doc_tree
.
children
[
i
],
tmp_revs_info
);
}
tmp_revs_info
.
shift
();
};
getWinnerRevsInfoRec
(
doc_tree
,
[]);
return
revs_info
;
};
priv
.
getConflicts
=
function
(
revision
,
doc_tree
)
{
var
conflicts
=
[],
getConflictsRec
;
getConflictsRec
=
function
(
doc_tree
)
{
var
i
;
if
(
doc_tree
.
rev
===
revision
)
{
return
;
}
if
(
doc_tree
.
children
.
length
===
0
)
{
if
(
doc_tree
.
status
!==
"
deleted
"
)
{
conflicts
.
push
(
doc_tree
.
rev
);
}
}
for
(
i
=
0
;
i
<
doc_tree
.
children
.
length
;
i
+=
1
)
{
getConflictsRec
(
doc_tree
.
children
[
i
]);
}
};
getConflictsRec
(
doc_tree
);
return
conflicts
.
length
===
0
?
undefined
:
conflicts
;
};
priv
.
get
=
function
(
doc
,
option
,
callback
)
{
priv
.
send
(
"
get
"
,
doc
,
option
,
callback
);
};
priv
.
put
=
function
(
doc
,
option
,
callback
)
{
priv
.
send
(
"
put
"
,
doc
,
option
,
callback
);
};
priv
.
remove
=
function
(
doc
,
option
,
callback
)
{
priv
.
send
(
"
remove
"
,
doc
,
option
,
callback
);
};
priv
.
getAttachment
=
function
(
attachment
,
option
,
callback
)
{
priv
.
send
(
"
getAttachment
"
,
attachment
,
option
,
callback
);
};
priv
.
putAttachment
=
function
(
attachment
,
option
,
callback
)
{
priv
.
send
(
"
putAttachment
"
,
attachment
,
option
,
callback
);
};
priv
.
removeAttachment
=
function
(
attachment
,
option
,
callback
)
{
priv
.
send
(
"
removeAttachment
"
,
attachment
,
option
,
callback
);
};
priv
.
getDocument
=
function
(
doc
,
option
,
callback
)
{
doc
=
priv
.
clone
(
doc
);
doc
.
_id
=
doc
.
_id
+
"
.
"
+
doc
.
_rev
;
delete
doc
.
_attachment
;
delete
doc
.
_rev
;
delete
doc
.
_revs
;
delete
doc
.
_revs_info
;
priv
.
get
(
doc
,
option
,
callback
);
};
priv
.
putDocument
=
function
(
doc
,
option
,
callback
)
{
doc
=
priv
.
clone
(
doc
);
doc
.
_id
=
doc
.
_id
+
"
.
"
+
doc
.
_rev
;
delete
doc
.
_attachment
;
delete
doc
.
_data
;
delete
doc
.
_mimetype
;
delete
doc
.
_rev
;
delete
doc
.
_revs
;
delete
doc
.
_revs_info
;
priv
.
put
(
doc
,
option
,
callback
);
};
priv
.
getRevisionTree
=
function
(
doc
,
option
,
callback
)
{
doc
=
priv
.
clone
(
doc
);
doc
.
_id
=
doc
.
_id
+
priv
.
doc_tree_suffix
;
priv
.
get
(
doc
,
option
,
callback
);
};
priv
.
getAttachmentList
=
function
(
doc
,
option
,
callback
)
{
var
attachment_id
,
dealResults
,
state
=
"
ok
"
,
result_list
=
[],
count
=
0
;
dealResults
=
function
(
attachment_id
,
attachment_meta
)
{
return
function
(
err
,
attachment
)
{
if
(
state
!==
"
ok
"
)
{
return
;
}
count
-=
1
;
if
(
err
)
{
if
(
err
.
status
===
404
)
{
result_list
.
push
(
undefined
);
}
else
{
state
=
"
error
"
;
return
callback
(
err
,
undefined
);
}
}
result_list
.
push
({
"
_attachment
"
:
attachment_id
,
"
_data
"
:
attachment
,
"
_mimetype
"
:
attachment_meta
.
content_type
});
if
(
count
===
0
)
{
state
=
"
finished
"
;
callback
(
undefined
,
result_list
);
}
};
};
for
(
attachment_id
in
doc
.
_attachments
)
{
if
(
doc
.
_attachments
.
hasOwnProperty
(
attachment_id
))
{
count
+=
1
;
priv
.
getAttachment
(
{
"
_id
"
:
doc
.
_id
,
"
_attachment
"
:
attachment_id
},
option
,
dealResults
(
attachment_id
,
doc
.
_attachments
[
attachment_id
])
);
}
}
if
(
count
===
0
)
{
callback
(
undefined
,
[]);
}
};
priv
.
putAttachmentList
=
function
(
doc
,
option
,
attachment_list
,
callback
)
{
var
i
,
dealResults
,
state
=
"
ok
"
,
count
=
0
,
attachment
;
attachment_list
=
attachment_list
||
[];
dealResults
=
function
(
index
)
{
return
function
(
err
,
response
)
{
if
(
state
!==
"
ok
"
)
{
return
;
}
count
-=
1
;
if
(
err
)
{
state
=
"
error
"
;
return
callback
(
err
,
undefined
);
}
if
(
count
===
0
)
{
state
=
"
finished
"
;
callback
(
undefined
,
{
"
id
"
:
doc
.
_id
,
"
ok
"
:
true
});
}
};
};
for
(
i
=
0
;
i
<
attachment_list
.
length
;
i
+=
1
)
{
attachment
=
attachment_list
[
i
];
if
(
attachment
!==
undefined
)
{
count
+=
1
;
attachment
.
_id
=
doc
.
_id
+
"
.
"
+
doc
.
_rev
;
priv
.
putAttachment
(
attachment
,
option
,
dealResults
(
i
));
}
}
if
(
count
===
0
)
{
return
callback
(
undefined
,
{
"
id
"
:
doc
.
_id
,
"
ok
"
:
true
});
}
};
priv
.
putDocumentTree
=
function
(
doc
,
option
,
doc_tree
,
callback
)
{
doc_tree
=
priv
.
clone
(
doc_tree
);
doc_tree
.
_id
=
doc
.
_id
+
priv
.
doc_tree_suffix
;
priv
.
put
(
doc_tree
,
option
,
callback
);
};
priv
.
notFoundError
=
function
(
message
,
reason
)
{
return
{
"
status
"
:
404
,
"
statusText
"
:
"
Not Found
"
,
"
error
"
:
"
not_found
"
,
"
message
"
:
message
,
"
reason
"
:
reason
};
};
priv
.
conflictError
=
function
(
message
,
reason
)
{
return
{
"
status
"
:
409
,
"
statusText
"
:
"
Conflict
"
,
"
error
"
:
"
conflict
"
,
"
message
"
:
message
,
"
reason
"
:
reason
};
};
priv
.
revisionGenericRequest
=
function
(
doc
,
option
,
specific_parameter
,
onEnd
)
{
var
prev_doc
,
doc_tree
,
attachment_list
,
callback
=
{};
if
(
specific_parameter
.
doc_id
)
{
doc
.
_id
=
specific_parameter
.
doc_id
;
}
if
(
specific_parameter
.
attachment_id
)
{
doc
.
_attachment
=
specific_parameter
.
attachment_id
;
}
callback
.
begin
=
function
()
{
var
check_error
;
doc
.
_id
=
doc
.
_id
||
priv
.
generateUuid
();
if
(
specific_parameter
.
revision_needed
&&
!
doc
.
_rev
)
{
return
onEnd
(
priv
.
conflictError
(
"
Document update conflict
"
,
"
No document revision was provided
"
),
undefined
);
}
// check revision format
check_error
=
priv
.
checkDocumentRevisionFormat
(
doc
);
if
(
check_error
!==
undefined
)
{
return
onEnd
(
check_error
,
undefined
);
}
priv
.
getRevisionTree
(
doc
,
option
,
callback
.
getRevisionTree
);
};
callback
.
getRevisionTree
=
function
(
err
,
response
)
{
var
winner_info
,
previous_revision
,
generate_new_revision
;
previous_revision
=
doc
.
_rev
;
generate_new_revision
=
doc
.
_revs
||
doc
.
_revs_info
?
false
:
true
;
if
(
err
)
{
if
(
err
.
status
!==
404
)
{
err
.
message
=
"
Cannot get document revision tree
"
;
return
onEnd
(
err
,
undefined
);
}
}
doc_tree
=
response
||
priv
.
newDocTree
();
if
(
specific_parameter
.
get
||
specific_parameter
.
getAttachment
)
{
if
(
!
doc
.
_rev
)
{
winner_info
=
priv
.
getWinnerRevsInfo
(
doc_tree
);
if
(
winner_info
.
length
===
0
)
{
return
onEnd
(
priv
.
notFoundError
(
"
Document not found
"
,
"
missing
"
),
undefined
);
}
if
(
winner_info
[
0
].
status
===
"
deleted
"
)
{
return
onEnd
(
priv
.
notFoundError
(
"
Document not found
"
,
"
deleted
"
),
undefined
);
}
doc
.
_rev
=
winner_info
[
0
].
rev
;
}
priv
.
fillDocumentRevisionProperties
(
doc
,
doc_tree
);
return
priv
.
getDocument
(
doc
,
option
,
callback
.
getDocument
);
}
priv
.
fillDocumentRevisionProperties
(
doc
,
doc_tree
);
if
(
generate_new_revision
)
{
if
(
previous_revision
&&
doc
.
_revs_info
.
length
===
0
)
{
// the document history has changed, it means that the document
// revision was wrong. Add a pseudo history to the document
doc
.
_rev
=
previous_revision
;
doc
.
_revs
=
{
"
start
"
:
parseInt
(
previous_revision
.
split
(
"
-
"
)[
0
],
10
),
"
ids
"
:
[
previous_revision
.
split
(
"
-
"
)[
1
]]
};
doc
.
_revs_info
=
[{
"
rev
"
:
previous_revision
,
"
status
"
:
"
missing
"
}];
}
doc
=
priv
.
generateNextRevision
(
doc
,
specific_parameter
.
remove
);
}
if
(
doc
.
_revs_info
.
length
>
1
)
{
prev_doc
=
{
"
_id
"
:
doc
.
_id
,
"
_rev
"
:
doc
.
_revs_info
[
1
].
rev
};
if
(
!
generate_new_revision
&&
specific_parameter
.
putAttachment
)
{
prev_doc
.
_rev
=
doc
.
_revs_info
[
0
].
rev
;
}
}
// force revs_info status
doc
.
_revs_info
[
0
].
status
=
(
specific_parameter
.
remove
?
"
deleted
"
:
"
available
"
);
priv
.
updateDocumentTree
(
doc
,
doc_tree
);
if
(
prev_doc
)
{
return
priv
.
getDocument
(
prev_doc
,
option
,
callback
.
getDocument
);
}
if
(
specific_parameter
.
remove
||
specific_parameter
.
removeAttachment
)
{
return
onEnd
(
priv
.
notFoundError
(
"
Unable to remove an inexistent document
"
,
"
missing
"
),
undefined
);
}
priv
.
putDocument
(
doc
,
option
,
callback
.
putDocument
);
};
callback
.
getDocument
=
function
(
err
,
res_doc
)
{
var
k
,
conflicts
;
if
(
err
)
{
if
(
err
.
status
===
404
)
{
if
(
specific_parameter
.
remove
||
specific_parameter
.
removeAttachment
)
{
return
onEnd
(
priv
.
conflictError
(
"
Document update conflict
"
,
"
Document is missing
"
),
undefined
);
}
if
(
specific_parameter
.
get
)
{
return
onEnd
(
priv
.
notFoundError
(
"
Unable to find the document
"
,
"
missing
"
),
undefined
);
}
res_doc
=
{};
}
else
{
err
.
message
=
"
Cannot get document
"
;
return
onEnd
(
err
,
undefined
);
}
}
if
(
specific_parameter
.
get
)
{
res_doc
.
_id
=
doc
.
_id
;
res_doc
.
_rev
=
doc
.
_rev
;
if
(
option
.
conflicts
===
true
)
{
conflicts
=
priv
.
getConflicts
(
doc
.
_rev
,
doc_tree
);
if
(
conflicts
)
{
res_doc
.
_conflicts
=
conflicts
;
}
}
if
(
option
.
revs
===
true
)
{
res_doc
.
_revisions
=
doc
.
_revs
;
}
if
(
option
.
revs_info
===
true
)
{
res_doc
.
_revs_info
=
doc
.
_revs_info
;
}
return
onEnd
(
undefined
,
res_doc
);
}
if
(
specific_parameter
.
putAttachment
||
specific_parameter
.
removeAttachment
)
{
// copy metadata (not beginning by "_" to document
for
(
k
in
res_doc
)
{
if
(
res_doc
.
hasOwnProperty
(
k
)
&&
!
k
.
match
(
"
^_
"
))
{
doc
[
k
]
=
res_doc
[
k
];
}
}
}
if
(
specific_parameter
.
remove
)
{
priv
.
putDocumentTree
(
doc
,
option
,
doc_tree
,
callback
.
putDocumentTree
);
}
else
{
priv
.
getAttachmentList
(
res_doc
,
option
,
callback
.
getAttachmentList
);
}
};
callback
.
getAttachmentList
=
function
(
err
,
res_list
)
{
var
i
,
attachment_found
=
false
;
if
(
err
)
{
err
.
message
=
"
Cannot get attachment
"
;
return
onEnd
(
err
,
undefined
);
}
attachment_list
=
res_list
||
[];
if
(
specific_parameter
.
getAttachment
)
{
// getting specific attachment
for
(
i
=
0
;
i
<
attachment_list
.
length
;
i
+=
1
)
{
if
(
attachment_list
[
i
]
&&
doc
.
_attachment
===
attachment_list
[
i
].
_attachment
)
{
return
onEnd
(
undefined
,
attachment_list
[
i
].
_data
);
}
}
return
onEnd
(
priv
.
notFoundError
(
"
Unable to get an inexistent attachment
"
,
"
missing
"
),
undefined
);
}
if
(
specific_parameter
.
remove_from_attachment_list
)
{
// removing specific attachment
for
(
i
=
0
;
i
<
attachment_list
.
length
;
i
+=
1
)
{
if
(
attachment_list
[
i
]
&&
specific_parameter
.
remove_from_attachment_list
.
_attachment
===
attachment_list
[
i
].
_attachment
)
{
attachment_found
=
true
;
attachment_list
[
i
]
=
undefined
;
break
;
}
}
if
(
!
attachment_found
)
{
return
onEnd
(
priv
.
notFoundError
(
"
Unable to remove an inexistent attachment
"
,
"
missing
"
),
undefined
);
}
}
priv
.
putDocument
(
doc
,
option
,
callback
.
putDocument
);
};
callback
.
putDocument
=
function
(
err
,
response
)
{
var
i
,
attachment_found
=
false
;
if
(
err
)
{
err
.
message
=
"
Cannot post the document
"
;
return
onEnd
(
err
,
undefined
);
}
if
(
specific_parameter
.
add_to_attachment_list
)
{
// adding specific attachment
attachment_list
=
attachment_list
||
[];
for
(
i
=
0
;
i
<
attachment_list
.
length
;
i
+=
1
)
{
if
(
attachment_list
[
i
]
&&
specific_parameter
.
add_to_attachment_list
.
_attachment
===
attachment_list
[
i
].
_attachment
)
{
attachment_found
=
true
;
attachment_list
[
i
]
=
specific_parameter
.
add_to_attachment_list
;
break
;
}
}
if
(
!
attachment_found
)
{
attachment_list
.
unshift
(
specific_parameter
.
add_to_attachment_list
);
}
}
priv
.
putAttachmentList
(
doc
,
option
,
attachment_list
,
callback
.
putAttachmentList
);
};
callback
.
putAttachmentList
=
function
(
err
,
response
)
{
if
(
err
)
{
err
.
message
=
"
Cannot copy attacments to the document
"
;
return
onEnd
(
err
,
undefined
);
}
priv
.
putDocumentTree
(
doc
,
option
,
doc_tree
,
callback
.
putDocumentTree
);
};
callback
.
putDocumentTree
=
function
(
err
,
response
)
{
var
response_object
;
if
(
err
)
{
err
.
message
=
"
Cannot update the document history
"
;
return
onEnd
(
err
,
undefined
);
}
response_object
=
{
"
ok
"
:
true
,
"
id
"
:
doc
.
_id
,
"
rev
"
:
doc
.
_rev
};
if
(
specific_parameter
.
putAttachment
||
specific_parameter
.
removeAttachment
||
specific_parameter
.
getAttachment
)
{
response_object
.
attachment
=
doc
.
_attachment
;
}
onEnd
(
undefined
,
response_object
);
// if (option.keep_revision_history !== true) {
// // priv.remove(prev_doc, option, function () {
// // - change "available" status to "deleted"
// // - remove attachments
// // - done, no callback
// // });
// }
};
callback
.
begin
();
};
/**
* Post the document metadata and create or update a document tree.
* Options:
* - {boolean} keep_revision_history To keep the previous revisions
* (false by default) (NYI).
* @method post
* @param {object} command The JIO command
*/
that
.
post
=
function
(
command
)
{
priv
.
revisionGenericRequest
(
command
.
cloneDoc
(),
command
.
cloneOption
(),
{},
function
(
err
,
response
)
{
if
(
err
)
{
return
that
.
error
(
err
);
}
that
.
success
(
response
);
}
);
};
/**
* Put the document metadata and create or update a document tree.
* Options:
* - {boolean} keep_revision_history To keep the previous revisions
* (false by default) (NYI).
* @method put
* @param {object} command The JIO command
*/
that
.
put
=
function
(
command
)
{
priv
.
revisionGenericRequest
(
command
.
cloneDoc
(),
command
.
cloneOption
(),
{},
function
(
err
,
response
)
{
if
(
err
)
{
return
that
.
error
(
err
);
}
that
.
success
(
response
);
}
);
};
that
.
putAttachment
=
function
(
command
)
{
priv
.
revisionGenericRequest
(
command
.
cloneDoc
(),
command
.
cloneOption
(),
{
"
doc_id
"
:
command
.
getDocId
(),
"
attachment_id
"
:
command
.
getAttachmentId
(),
"
add_to_attachment_list
"
:
{
"
_attachment
"
:
command
.
getAttachmentId
(),
"
_mimetype
"
:
command
.
getAttachmentMimeType
(),
"
_data
"
:
command
.
getAttachmentData
()
},
"
putAttachment
"
:
true
},
function
(
err
,
response
)
{
if
(
err
)
{
return
that
.
error
(
err
);
}
that
.
success
(
response
);
}
);
};
that
.
remove
=
function
(
command
)
{
if
(
command
.
getAttachmentId
())
{
return
that
.
removeAttachment
(
command
);
}
priv
.
revisionGenericRequest
(
command
.
cloneDoc
(),
command
.
cloneOption
(),
{
"
revision_needed
"
:
true
,
"
remove
"
:
true
},
function
(
err
,
response
)
{
if
(
err
)
{
return
that
.
error
(
err
);
}
that
.
success
(
response
);
}
);
};
that
.
removeAttachment
=
function
(
command
)
{
priv
.
revisionGenericRequest
(
command
.
cloneDoc
(),
command
.
cloneOption
(),
{
"
doc_id
"
:
command
.
getDocId
(),
"
attachment_id
"
:
command
.
getAttachmentId
(),
"
revision_needed
"
:
true
,
"
removeAttachment
"
:
true
,
"
remove_from_attachment_list
"
:
{
"
_attachment
"
:
command
.
getAttachmentId
()
}
},
function
(
err
,
response
)
{
if
(
err
)
{
return
that
.
error
(
err
);
}
that
.
success
(
response
);
}
);
};
that
.
get
=
function
(
command
)
{
if
(
command
.
getAttachmentId
())
{
return
that
.
getAttachment
(
command
);
}
priv
.
revisionGenericRequest
(
command
.
cloneDoc
(),
command
.
cloneOption
(),
{
"
get
"
:
true
},
function
(
err
,
response
)
{
if
(
err
)
{
return
that
.
error
(
err
);
}
that
.
success
(
response
);
}
);
};
that
.
getAttachment
=
function
(
command
)
{
priv
.
revisionGenericRequest
(
command
.
cloneDoc
(),
command
.
cloneOption
(),
{
"
doc_id
"
:
command
.
getDocId
(),
"
attachment_id
"
:
command
.
getAttachmentId
(),
"
getAttachment
"
:
true
},
function
(
err
,
response
)
{
if
(
err
)
{
return
that
.
error
(
err
);
}
that
.
success
(
response
);
}
);
};
that
.
allDocs
=
function
(
command
)
{
var
rows
,
result
=
{
"
total_rows
"
:
0
,
"
rows
"
:
[]},
functions
=
{};
functions
.
finished
=
0
;
functions
.
falseResponseGenerator
=
function
(
response
,
callback
)
{
callback
(
undefined
,
response
);
};
functions
.
fillResultGenerator
=
function
(
doc_id
)
{
return
function
(
err
,
doc_tree
)
{
var
document_revision
,
row
,
revs_info
;
if
(
err
)
{
return
that
.
error
(
err
);
}
revs_info
=
priv
.
getWinnerRevsInfo
(
doc_tree
);
document_revision
=
rows
.
document_revisions
[
doc_id
+
"
.
"
+
revs_info
[
0
].
rev
];
if
(
document_revision
)
{
row
=
{
"
id
"
:
doc_id
,
"
key
"
:
doc_id
,
"
value
"
:
{
"
rev
"
:
revs_info
[
0
].
rev
}
};
if
(
document_revision
.
doc
&&
command
.
getOption
(
"
include_docs
"
))
{
document_revision
.
doc
.
_id
=
doc_id
;
document_revision
.
doc
.
_rev
=
revs_info
[
0
].
rev
;
row
.
doc
=
document_revision
.
doc
;
}
result
.
rows
.
push
(
row
);
result
.
total_rows
+=
1
;
}
functions
.
success
();
};
};
functions
.
success
=
function
()
{
functions
.
finished
-=
1
;
if
(
functions
.
finished
===
0
)
{
that
.
success
(
result
);
}
};
priv
.
send
(
"
allDocs
"
,
null
,
command
.
cloneOption
(
),
function
(
err
,
response
)
{
var
i
,
j
,
row
,
selector
,
selected
;
if
(
err
)
{
return
that
.
error
(
err
);
}
selector
=
/
\.
revision_tree
\.
json$/
;
rows
=
{
"
revision_trees
"
:
{
// id.revision_tree.json: {
// id: blabla
// doc: {...}
// }
},
"
document_revisions
"
:
{
// id.rev: {
// id: blabla
// rev: 1-1
// doc: {...}
// }
}
};
while
(
response
.
rows
.
length
>
0
)
{
// filling rows
row
=
response
.
rows
.
shift
();
selected
=
selector
.
exec
(
row
.
id
);
if
(
selected
)
{
selected
=
selected
.
input
.
substring
(
0
,
selected
.
index
);
// this is a revision tree
rows
.
revision_trees
[
row
.
id
]
=
{
"
id
"
:
selected
};
if
(
row
.
doc
)
{
rows
.
revision_trees
[
row
.
id
].
doc
=
row
.
doc
;
}
}
else
{
// this is a simple revision
rows
.
document_revisions
[
row
.
id
]
=
{
"
id
"
:
row
.
id
.
split
(
"
.
"
).
slice
(
0
,
-
1
),
"
rev
"
:
row
.
id
.
split
(
"
.
"
).
slice
(
-
1
)
};
if
(
row
.
doc
)
{
rows
.
document_revisions
[
row
.
id
].
doc
=
row
.
doc
;
}
}
}
functions
.
finished
+=
1
;
for
(
i
in
rows
.
revision_trees
)
{
if
(
rows
.
revision_trees
.
hasOwnProperty
(
i
))
{
functions
.
finished
+=
1
;
if
(
rows
.
revision_trees
[
i
].
doc
)
{
functions
.
falseResponseGenerator
(
rows
.
revision_trees
[
i
].
doc
,
functions
.
fillResultGenerator
(
rows
.
revision_trees
[
i
].
id
)
);
}
else
{
priv
.
getRevisionTree
(
{
"
_id
"
:
rows
.
revision_trees
[
i
].
id
},
command
.
cloneOption
(),
functions
.
fillResultGenerator
(
rows
.
revision_trees
[
i
].
id
)
);
}
}
}
functions
.
success
();
});
};
// END //
return
that
;
});
// end RevisionStorage
}));
src/jio.storage/s3storage.js
deleted
100644 → 0
View file @
3356688e
/*jslint indent: 2, maxlen: 80, nomen: true */
/*global define, jIO, btoa, b64_hmac_sha1, jQuery, XMLHttpRequest, XHRwrapper,
FormData*/
/**
* JIO S3 Storage. Type = "s3".
* Amazon S3 "database" storage.
*/
// define([module_name], [dependencies], module);
(
function
(
dependencies
,
module
)
{
"
use strict
"
;
if
(
typeof
define
===
'
function
'
&&
define
.
amd
)
{
return
define
(
dependencies
,
module
);
}
module
(
jIO
,
jQuery
,
{
b64_hmac_sha1
:
b64_hmac_sha1
});
}([
'
jio
'
,
'
jquery
'
,
'
sha1
'
],
function
(
jIO
,
$
,
sha1
)
{
"
use strict
"
;
var
b64_hmac_sha1
=
sha1
.
b64_hmac_sha1
;
jIO
.
addStorageType
(
"
s3
"
,
function
(
spec
,
my
)
{
var
evt
,
that
,
priv
=
{};
spec
=
spec
||
{};
that
=
my
.
basicStorage
(
spec
,
my
);
// attributes
priv
.
username
=
spec
.
username
||
''
;
priv
.
AWSIdentifier
=
spec
.
AWSIdentifier
||
''
;
priv
.
password
=
spec
.
password
||
''
;
priv
.
server
=
spec
.
server
||
''
;
/*|| jiobucket ||*/
priv
.
acl
=
spec
.
acl
||
''
;
/*||> "private,
public-read,
public-read-write,
authenticated-read,
bucket-owner-read,
bucket-owner-full-control" <||*/
priv
.
actionStatus
=
spec
.
actionStatus
||
''
;
priv
.
contenTType
=
spec
.
contenTType
||
''
;
/**
* Update [doc] the document object and remove [doc] keys
* which are not in [new_doc]. It only changes [doc] keys not starting
* with an underscore.
* ex: doc: {key:value1,_key:value2} with
* new_doc: {key:value3,_key:value4} updates
* doc: {key:value3,_key:value2}.
* @param {object} doc The original document object.
* @param {object} new_doc The new document object
**/
priv
.
secureDocId
=
function
(
string
)
{
var
split
=
string
.
split
(
'
/
'
),
i
;
if
(
split
[
0
]
===
''
)
{
split
=
split
.
slice
(
1
);
}
for
(
i
=
0
;
i
<
split
.
length
;
i
+=
1
)
{
if
(
split
[
i
]
===
''
)
{
return
''
;
}
}
return
split
.
join
(
'
%2F
'
);
};
/**
* Replace substrings to another strings
* @method recursiveReplace
* @param {string} string The string to do replacement
* @param {array} list_of_replacement An array of couple
* ["substring to select", "selected substring replaced by this string"].
* @return {string} The replaced string
*/
priv
.
recursiveReplace
=
function
(
string
,
list_of_replacement
)
{
var
i
,
split_string
=
string
.
split
(
list_of_replacement
[
0
][
0
]);
if
(
list_of_replacement
[
1
])
{
for
(
i
=
0
;
i
<
split_string
.
length
;
i
+=
1
)
{
split_string
[
i
]
=
priv
.
recursiveReplace
(
split_string
[
i
],
list_of_replacement
.
slice
(
1
)
);
}
}
return
split_string
.
join
(
list_of_replacement
[
0
][
1
]);
};
/**
* Changes / to %2F, % to %25 and . to _.
* @method secureName
* @param {string} name The name to secure
* @return {string} The secured name
*/
priv
.
secureName
=
function
(
name
)
{
return
priv
.
recursiveReplace
(
name
,
[[
"
/
"
,
"
%2F
"
],
[
"
%
"
,
"
%25
"
]]);
};
/**
* Restores the original name from a secured name
* @method restoreName
* @param {string} secured_name The secured name to restore
* @return {string} The original name
*/
priv
.
restoreName
=
function
(
secured_name
)
{
return
priv
.
recursiveReplace
(
secured_name
,
[[
"
%2F
"
,
"
/
"
],
[
"
%25
"
,
"
%
"
]]);
};
/**
* Convert document id and attachment id to a file name
* @method idsToFileName
* @param {string} doc_id The document id
* @param {string} attachment_id The attachment id (optional)
* @return {string} The file name
*/
priv
.
idsToFileName
=
function
(
doc_id
,
attachment_id
)
{
doc_id
=
priv
.
secureName
(
doc_id
).
split
(
"
.
"
).
join
(
"
_.
"
);
if
(
typeof
attachment_id
===
"
string
"
)
{
attachment_id
=
priv
.
secureName
(
attachment_id
).
split
(
"
.
"
).
join
(
"
_.
"
);
return
doc_id
+
"
.
"
+
attachment_id
;
}
return
doc_id
;
};
/**
* Convert a file name to a document id (and attachment id if there)
* @method fileNameToIds
* @param {string} file_name The file name to convert
* @return {array} ["document id", "attachment id"] or ["document id"]
*/
priv
.
fileNameToIds
=
function
(
file_name
)
{
var
separator_index
=
-
1
,
split
=
file_name
.
split
(
"
.
"
);
split
.
slice
(
0
,
-
1
).
forEach
(
function
(
file_name_part
,
index
)
{
if
(
file_name_part
.
slice
(
-
1
)
!==
"
_
"
)
{
separator_index
=
index
;
}
});
if
(
separator_index
===
-
1
)
{
return
[
priv
.
restoreName
(
priv
.
restoreName
(
file_name
).
split
(
"
_.
"
).
join
(
"
.
"
))];
}
return
[
priv
.
restoreName
(
priv
.
restoreName
(
split
.
slice
(
0
,
separator_index
+
1
).
join
(
"
.
"
)
).
split
(
"
_.
"
).
join
(
"
.
"
)),
priv
.
restoreName
(
priv
.
restoreName
(
split
.
slice
(
separator_index
+
1
).
join
(
"
.
"
)
).
split
(
"
_.
"
).
join
(
"
.
"
))
];
};
/**
* Removes the last character if it is a "/". "/a/b/c/" become "/a/b/c"
* @method removeSlashIfLast
* @param {string} string The string to modify
* @return {string} The modified string
*/
priv
.
removeSlashIfLast
=
function
(
string
)
{
if
(
string
[
string
.
length
-
1
]
===
"
/
"
)
{
return
string
.
slice
(
0
,
-
1
);
}
return
string
;
};
that
.
documentObjectUpdate
=
function
(
doc
,
new_doc
)
{
var
k
;
for
(
k
in
doc
)
{
if
(
doc
.
hasOwnProperty
(
k
))
{
if
(
k
[
0
]
!==
'
_
'
)
{
delete
doc
[
k
];
}
}
}
for
(
k
in
new_doc
)
{
if
(
new_doc
.
hasOwnProperty
(
k
))
{
if
(
k
[
0
]
!==
'
_
'
)
{
doc
[
k
]
=
new_doc
[
k
];
}
}
}
};
/**
* Checks if an object has no enumerable keys
* @method objectIsEmpty
* @param {object} obj The object
* @return {boolean} true if empty, else false
*/
that
.
objectIsEmpty
=
function
(
obj
)
{
var
k
;
for
(
k
in
obj
)
{
if
(
obj
.
hasOwnProperty
(
k
))
{
return
false
;
}
}
return
true
;
};
// ===================== overrides ======================
that
.
specToStore
=
function
()
{
return
{
"
username
"
:
priv
.
username
,
"
password
"
:
priv
.
password
,
"
server
"
:
priv
.
server
,
"
acl
"
:
priv
.
acl
};
};
that
.
validateState
=
function
()
{
// xxx complete error message
// jjj completion below
if
(
typeof
priv
.
AWSIdentifier
===
"
string
"
&&
priv
.
AWSIdentifier
===
''
)
{
return
'
Need at least one parameter "Aws login".
'
;
}
if
(
typeof
priv
.
password
===
"
string
"
&&
priv
.
password
===
''
)
{
return
'
Need at least one parameter "password".
'
;
}
if
(
typeof
priv
.
server
===
"
string
"
&&
priv
.
server
===
''
)
{
return
'
Need at least one parameter "server".
'
;
}
return
''
;
};
// =================== S3 Specifics =================
/**
* Encoding the signature using a stringToSign
* Encoding the policy
* @method buildStringToSign
* @param {string} http_verb The HTTP method
* @param {string} content_md5 The md5 content
* @param {string} content_type The content type
* @param {number} expires The expires time
* @param {string} x_amz_headers The specific amazon headers
* @param {string} path_key The path of the document
* @return {string} The generated signature
*/
// xxx no need to make it public, use private -> "priv" (not "that")
priv
.
buildStringToSign
=
function
(
http_verb
,
content_md5
,
content_type
,
expires
,
x_amz_headers
,
path_key
)
{
//example :
// var StringToSign = S3.buildStringToSign(S3.httpVerb,'','','',
// 'x-amz-date:'+S3.requestUTC,'/jio1st/prive.json');
var
StringToSign
=
http_verb
+
'
\n
'
+
content_md5
+
'
\n
'
//content-md5
+
content_type
+
'
\n
'
//content-type
+
expires
+
'
\n
'
//expires
+
x_amz_headers
+
'
\n
'
//x-amz headers
+
path_key
;
//path key
return
StringToSign
;
};
that
.
encodePolicy
=
function
(
form
)
{
//generates the policy
//enables the choice for the http response code
var
http_code
,
s3_policy
,
Signature
=
''
;
s3_policy
=
{
"
expiration
"
:
"
2020-01-01T00:00:00Z
"
,
"
conditions
"
:
[
{
"
bucket
"
:
priv
.
server
},
[
"
starts-with
"
,
"
$key
"
,
""
],
{
"
acl
"
:
priv
.
acl
},
{
"
success_action_redirect
"
:
""
},
{
"
success_action_status
"
:
http_code
},
[
"
starts-with
"
,
"
$Content-Type
"
,
""
],
[
"
content-length-range
"
,
0
,
524288000
]
]
};
//base64 encoding of the policy (native base64 js >>
// .btoa() = encode, .atob() = decode)
priv
.
b64_policy
=
btoa
(
JSON
.
stringify
(
s3_policy
));
//generates the signature value using the policy and the secret access key
//use of sha1.js to generate the signature
Signature
=
that
.
signature
(
priv
.
b64_policy
);
};
that
.
signature
=
function
(
string
)
{
var
Signature
=
b64_hmac_sha1
(
priv
.
password
,
string
);
return
Signature
;
};
function
xhr_onreadystatechange
(
docId
,
command
,
obj
,
http
,
jio
,
isAttachment
,
callback
)
{
obj
.
onreadystatechange
=
function
()
{
var
response
,
err
=
''
;
if
(
obj
.
readyState
===
4
)
{
if
(
this
.
status
===
204
||
this
.
status
===
201
||
this
.
status
===
200
)
{
switch
(
http
)
{
case
"
POST
"
:
that
.
success
({
ok
:
true
,
id
:
docId
});
break
;
case
'
PUT
'
:
if
(
jio
===
true
)
{
that
.
success
({
ok
:
true
,
id
:
command
.
getDocId
()
});
}
else
{
callback
(
this
.
responseText
);
}
break
;
case
'
GET
'
:
if
(
jio
===
true
)
{
if
(
typeof
this
.
responseText
!==
'
string
'
)
{
response
=
JSON
.
parse
(
this
.
responseText
);
response
.
_attachments
=
response
.
_attachments
||
{};
delete
response
.
_attachments
;
that
.
success
(
JSON
.
stringify
(
response
));
}
else
{
if
(
isAttachment
===
true
)
{
that
.
success
(
this
.
responseText
);
}
else
{
that
.
success
(
JSON
.
parse
(
this
.
responseText
));
}
}
}
else
{
callback
(
this
.
responseText
);
}
break
;
case
'
DELETE
'
:
if
(
jio
===
true
)
{
if
(
isAttachment
===
false
)
{
that
.
success
({
ok
:
true
,
id
:
command
.
getDocId
()
});
}
else
{
that
.
success
({
ok
:
true
,
id
:
command
.
getDocId
(),
attachment
:
command
.
getAttachmentId
()
});
}
}
else
{
callback
(
this
.
responseText
);
}
break
;
}
}
else
{
err
=
this
;
if
(
this
.
status
===
405
)
{
//status
//statustext "Not Found"
//error
//reason "reason"
//message "did not work"
err
.
error
=
"
not_allowed
"
;
that
.
error
(
err
);
}
if
(
this
.
status
===
404
)
{
if
(
http
===
'
GET
'
)
{
if
(
jio
===
true
)
{
//status
//statustext "Not Found"
//error
//reason "reason"
//message "did not work"
err
.
statustext
=
"
not_foud
"
;
err
.
reason
=
"
file does not exist
"
;
err
.
error
=
"
not_found
"
;
that
.
error
(
err
);
}
else
{
callback
(
'
404
'
);
}
}
else
{
//status
//statustext "Not Found"
//error
//reason "reason"
//message "did not work"
err
.
error
=
"
not_found
"
;
that
.
error
(
err
);
}
}
if
(
this
.
status
===
409
)
{
//status
//statustext "Not Found"
//error
//reason "reason"
//message "did not work"
err
.
error
=
"
already_exists
"
;
that
.
error
(
err
);
}
}
}
};
}
priv
.
updateMeta
=
function
(
doc
,
docid
,
attachid
,
action
,
data
)
{
doc
.
_attachments
=
doc
.
_attachments
||
{};
switch
(
action
)
{
case
"
add
"
:
doc
.
_attachments
[
attachid
]
=
data
;
//nothing happens
doc
=
JSON
.
stringify
(
doc
);
break
;
case
"
remove
"
:
if
(
doc
.
_attachments
!==
undefined
)
{
delete
doc
.
_attachments
[
attachid
];
}
doc
=
JSON
.
stringify
(
doc
);
break
;
case
"
update
"
:
doc
.
_attachments
[
attachid
]
=
data
;
//update happened in the put request
doc
=
JSON
.
stringify
(
doc
);
break
;
}
return
doc
;
};
priv
.
createError
=
function
(
status
,
message
,
reason
)
{
var
error
=
{
"
status
"
:
status
,
"
message
"
:
message
,
"
reason
"
:
reason
};
switch
(
status
)
{
case
404
:
error
.
statusText
=
"
Not found
"
;
break
;
case
405
:
error
.
statusText
=
"
Method Not Allowed
"
;
break
;
case
409
:
error
.
statusText
=
"
Conflicts
"
;
break
;
case
24
:
error
.
statusText
=
"
Corrupted Document
"
;
break
;
}
error
.
error
=
error
.
statusText
.
toLowerCase
().
split
(
"
"
).
join
(
"
_
"
);
return
error
;
};
that
.
encodeAuthorization
=
function
(
key
,
mime
)
{
//GET oriented method
var
requestUTC
,
httpVerb
,
StringToSign
,
Signature
;
requestUTC
=
new
Date
().
toUTCString
();
httpVerb
=
"
GET
"
;
StringToSign
=
priv
.
buildStringToSign
(
httpVerb
,
''
,
'
application/json
'
,
''
,
'
x-amz-date:
'
+
requestUTC
,
'
/
'
+
priv
.
server
+
'
/
'
+
key
);
Signature
=
b64_hmac_sha1
(
priv
.
password
,
StringToSign
);
return
Signature
;
};
that
.
XHRwrapper
=
function
(
command
,
docId
,
attachId
,
http
,
mime
,
data
,
jio
,
is_attachment
,
callback
)
{
var
docFile
,
requestUTC
,
StringToSign
,
url
,
Signature
,
xhr
;
docFile
=
priv
.
secureName
(
priv
.
idsToFileName
(
docId
,
attachId
||
undefined
));
requestUTC
=
new
Date
().
toUTCString
();
StringToSign
=
priv
.
buildStringToSign
(
http
,
''
,
mime
,
''
,
'
x-amz-date:
'
+
requestUTC
,
'
/
'
+
priv
.
server
+
'
/
'
+
docFile
);
url
=
'
http://s3.amazonaws.com/
'
+
priv
.
server
+
'
/
'
+
docFile
;
Signature
=
b64_hmac_sha1
(
priv
.
password
,
StringToSign
);
xhr
=
new
XMLHttpRequest
();
xhr
.
open
(
http
,
url
,
true
);
xhr
.
setRequestHeader
(
"
HTTP-status-code
"
,
"
100
"
);
xhr
.
setRequestHeader
(
"
x-amz-date
"
,
requestUTC
);
xhr
.
setRequestHeader
(
"
Authorization
"
,
"
AWS
"
+
priv
.
AWSIdentifier
+
"
:
"
+
Signature
);
xhr
.
setRequestHeader
(
"
Content-Type
"
,
mime
);
xhr
.
responseType
=
'
text
'
;
xhr_onreadystatechange
(
docId
,
command
,
xhr
,
http
,
jio
,
is_attachment
,
callback
);
if
(
http
===
'
PUT
'
)
{
xhr
.
send
(
data
);
}
else
{
xhr
.
send
(
null
);
}
};
// ==================== commands ====================
/**
* Create a document in local storage.
* @method post
* @param {object} command The JIO command
**/
that
.
post
=
function
(
command
)
{
//as S3 encoding key are directly inserted within the FormData(),
//use of XHRwrapper function ain't pertinent
var
doc
,
doc_id
,
mime
;
doc
=
command
.
cloneDoc
();
doc_id
=
command
.
getDocId
();
function
postDocument
()
{
var
http_response
,
fd
,
Signature
,
xhr
;
doc_id
=
priv
.
secureName
(
priv
.
idsToFileName
(
doc_id
));
//Meant to deep-serialize in order to avoid
//conflicts due to the multipart enctype
doc
=
JSON
.
stringify
(
doc
);
http_response
=
''
;
fd
=
new
FormData
();
//virtually builds the form fields
//filename
fd
.
append
(
'
key
'
,
doc_id
);
//file access authorizations
priv
.
acl
=
""
;
fd
.
append
(
'
acl
'
,
priv
.
acl
);
//content-type
priv
.
contenTType
=
"
text/plain
"
;
fd
.
append
(
'
Content-Type
'
,
priv
.
contenTType
);
//allows specification of a success url redirection
fd
.
append
(
'
success_action_redirect
'
,
''
);
//allows to specify the http code response if the request is successful
fd
.
append
(
'
success_action_status
'
,
http_response
);
//login AWS
fd
.
append
(
'
AWSAccessKeyId
'
,
priv
.
AWSIdentifier
);
//exchange policy with the amazon s3 service
//can be common to all uploads or specific
that
.
encodePolicy
(
fd
);
//priv.b64_policy = that.encodePolicy(fd);
fd
.
append
(
'
policy
'
,
priv
.
b64_policy
);
//signature through the base64.hmac.sha1(secret key, policy) method
Signature
=
b64_hmac_sha1
(
priv
.
password
,
priv
.
b64_policy
);
fd
.
append
(
'
signature
'
,
Signature
);
//uploaded content !!may must be a string rather than an object
fd
.
append
(
'
file
'
,
doc
);
xhr
=
new
XMLHttpRequest
();
xhr_onreadystatechange
(
doc_id
,
command
,
xhr
,
'
POST
'
,
true
,
false
,
''
);
xhr
.
open
(
'
POST
'
,
'
https://
'
+
priv
.
server
+
'
.s3.amazonaws.com/
'
,
true
);
xhr
.
send
(
fd
);
}
if
(
doc_id
===
''
||
doc_id
===
undefined
)
{
doc_id
=
'
no_document_id_
'
+
((
Math
.
random
()
*
10
).
toString
().
split
(
'
.
'
))[
1
];
doc
.
_id
=
doc_id
;
}
mime
=
'
text/plain; charset=UTF-8
'
;
that
.
XHRwrapper
(
command
,
doc_id
,
''
,
'
GET
'
,
mime
,
''
,
false
,
false
,
function
(
response
)
{
if
(
response
===
'
404
'
)
{
postDocument
();
}
else
{
//si ce n'est pas une 404,
//alors on renvoit une erreur 405
return
that
.
error
(
priv
.
createError
(
409
,
"
Cannot create document
"
,
"
Document already exists
"
));
}
}
);
};
/**
* Get a document or attachment
* @method get
* @param {object} command The JIO command
**/
that
.
get
=
function
(
command
)
{
var
docId
,
attachId
,
isJIO
,
mime
;
docId
=
command
.
getDocId
();
attachId
=
command
.
getAttachmentId
()
||
''
;
isJIO
=
true
;
mime
=
'
text/plain; charset=UTF-8
'
;
that
.
XHRwrapper
(
command
,
docId
,
attachId
,
'
GET
'
,
mime
,
''
,
isJIO
,
false
);
};
that
.
getAttachment
=
function
(
command
)
{
var
docId
,
attachId
,
isJIO
,
mime
;
docId
=
command
.
getDocId
();
attachId
=
command
.
getAttachmentId
();
isJIO
=
true
;
mime
=
'
text/plain; charset=UTF-8
'
;
that
.
XHRwrapper
(
command
,
docId
,
attachId
,
'
GET
'
,
mime
,
''
,
isJIO
,
true
);
};
/**
* Create or update a document in local storage.
* @method put
* @param {object} command The JIO command
**/
that
.
put
=
function
(
command
)
{
var
doc
,
docId
,
mime
;
doc
=
command
.
cloneDoc
();
docId
=
command
.
getDocId
();
mime
=
'
text/plain; charset=UTF-8
'
;
//pas d'attachment dans un put simple
function
putDocument
()
{
var
attachId
,
data
,
isJIO
;
attachId
=
''
;
data
=
JSON
.
stringify
(
doc
);
isJIO
=
true
;
that
.
XHRwrapper
(
command
,
docId
,
attachId
,
'
PUT
'
,
mime
,
data
,
isJIO
,
false
);
}
that
.
XHRwrapper
(
command
,
docId
,
''
,
'
GET
'
,
mime
,
''
,
false
,
false
,
function
(
response
)
{
//if (response === '404') {}
if
(
response
.
_attachments
!==
undefined
)
{
doc
.
_attachments
=
response
.
_attachments
;
}
putDocument
();
}
);
};
that
.
putAttachment
=
function
(
command
)
{
var
mon_document
,
docId
,
attachId
,
mime
,
attachment_id
,
attachment_data
,
attachment_md5
,
attachment_mimetype
,
attachment_length
;
mon_document
=
null
;
docId
=
command
.
getDocId
();
attachId
=
command
.
getAttachmentId
()
||
''
;
mime
=
'
text/plain; charset=UTF-8
'
;
//récupération des variables de l'attachement
attachment_id
=
command
.
getAttachmentId
();
attachment_data
=
command
.
getAttachmentData
();
attachment_md5
=
command
.
md5SumAttachmentData
();
attachment_mimetype
=
command
.
getAttachmentMimeType
();
attachment_length
=
command
.
getAttachmentLength
();
function
putAttachment
()
{
that
.
XHRwrapper
(
command
,
docId
,
attachId
,
'
PUT
'
,
mime
,
attachment_data
,
false
,
true
,
function
(
reponse
)
{
that
.
success
({
// response
"
ok
"
:
true
,
"
id
"
:
docId
,
"
attachment
"
:
attachId
//"rev": current_revision
});
}
);
}
function
putDocument
()
{
var
attachment_obj
,
data
,
doc
;
attachment_obj
=
{
//"revpos": 3, // optional
"
digest
"
:
attachment_md5
,
"
content_type
"
:
attachment_mimetype
,
"
length
"
:
attachment_length
};
data
=
JSON
.
parse
(
mon_document
);
doc
=
priv
.
updateMeta
(
data
,
docId
,
attachId
,
"
add
"
,
attachment_obj
);
that
.
XHRwrapper
(
command
,
docId
,
''
,
'
PUT
'
,
mime
,
doc
,
false
,
false
,
function
(
reponse
)
{
putAttachment
();
}
);
}
function
getDocument
()
{
//XHRwrapper(command,'PUT','text/plain; charset=UTF-8',true);
that
.
XHRwrapper
(
command
,
docId
,
''
,
'
GET
'
,
mime
,
''
,
false
,
false
,
function
(
reponse
)
{
if
(
reponse
===
'
404
'
)
{
return
that
.
error
(
priv
.
createError
(
404
,
"
Cannot find document
"
,
"
Document does not exist
"
));
}
mon_document
=
reponse
;
putDocument
();
}
);
}
getDocument
();
};
/**
* Remove a document or attachment
* @method remove
* @param {object} command The JIO command
*/
that
.
remove
=
function
(
command
)
{
var
docId
,
mime
;
docId
=
command
.
getDocId
();
mime
=
'
text/plain; charset=UTF-8
'
;
function
deleteDocument
()
{
that
.
XHRwrapper
(
command
,
docId
,
''
,
'
DELETE
'
,
mime
,
''
,
true
,
false
,
function
(
reponse
)
{
that
.
success
({
// response
"
ok
"
:
true
,
"
id
"
:
docId
//"rev": current_revision
});
}
);
}
function
myCallback
(
response
)
{
}
that
.
XHRwrapper
(
command
,
docId
,
''
,
'
GET
'
,
mime
,
''
,
false
,
false
,
function
(
response
)
{
var
attachKeys
,
keys
;
attachKeys
=
(
JSON
.
parse
(
response
)).
_attachments
;
for
(
keys
in
attachKeys
)
{
if
(
attachKeys
.
hasOwnProperty
(
keys
))
{
that
.
XHRwrapper
(
command
,
docId
,
keys
,
'
DELETE
'
,
mime
,
''
,
false
,
false
,
myCallback
);
}
}
deleteDocument
();
}
);
};
that
.
removeAttachment
=
function
(
command
)
{
var
mon_document
,
docId
,
attachId
,
mime
,
attachment_id
,
attachment_data
,
attachment_md5
,
attachment_mimetype
,
attachment_length
;
mon_document
=
null
;
docId
=
command
.
getDocId
();
attachId
=
command
.
getAttachmentId
()
||
''
;
mime
=
'
text/plain; charset=UTF-8
'
;
//récupération des variables de l'attachement
attachment_id
=
command
.
getAttachmentId
();
attachment_data
=
command
.
getAttachmentData
();
attachment_md5
=
command
.
md5SumAttachmentData
();
attachment_mimetype
=
command
.
getAttachmentMimeType
();
attachment_length
=
command
.
getAttachmentLength
();
function
removeAttachment
()
{
that
.
XHRwrapper
(
command
,
docId
,
attachId
,
'
DELETE
'
,
mime
,
''
,
true
,
true
,
function
(
reponse
)
{
}
);
}
function
putDocument
()
{
var
data
,
doc
;
data
=
JSON
.
parse
(
mon_document
);
doc
=
priv
.
updateMeta
(
data
,
docId
,
attachId
,
"
remove
"
,
''
);
that
.
XHRwrapper
(
command
,
docId
,
''
,
'
PUT
'
,
mime
,
doc
,
false
,
false
,
function
(
reponse
)
{
removeAttachment
();
}
);
}
function
getDocument
()
{
that
.
XHRwrapper
(
command
,
docId
,
''
,
'
GET
'
,
mime
,
''
,
false
,
false
,
function
(
reponse
)
{
mon_document
=
reponse
;
putDocument
();
}
);
}
getDocument
();
};
/**
* Get all filenames belonging to a user from the document index
* @method allDocs
* @param {object} command The JIO command
**/
that
.
allDocs
=
function
(
command
)
{
var
mon_document
,
mime
;
mon_document
=
null
;
mime
=
'
text/plain; charset=UTF-8
'
;
function
makeJSON
()
{
var
keys
,
resultTable
,
counter
,
allDocResponse
,
count
,
countB
,
dealCallback
,
errCallback
,
i
,
keyId
,
Signature
,
callURL
,
requestUTC
,
parse
,
checkCounter
;
keys
=
$
(
mon_document
).
find
(
'
Key
'
);
resultTable
=
[];
counter
=
0
;
keys
.
each
(
function
(
index
)
{
var
that
,
filename
,
docId
;
that
=
$
(
this
);
filename
=
that
.
context
.
textContent
;
docId
=
priv
.
idsToFileName
(
priv
.
fileNameToIds
(
filename
)[
0
]);
if
(
counter
===
0
)
{
counter
+=
1
;
resultTable
.
push
(
docId
);
}
else
if
(
docId
!==
resultTable
[
counter
-
1
])
{
counter
+=
1
;
resultTable
.
push
(
docId
);
}
});
allDocResponse
=
{
// document content will be added to response
"
total_rows
"
:
resultTable
.
length
,
"
offset
"
:
0
,
"
rows
"
:
[]
};
//needed to save the index within the $.ajax.success() callback
count
=
resultTable
.
length
-
1
;
countB
=
0
;
dealCallback
=
function
(
i
,
countB
,
allDoc
)
{
return
function
(
doc
,
statustext
,
response
)
{
allDoc
.
rows
[
i
].
doc
=
response
.
responseText
;
if
(
count
===
0
)
{
that
.
success
(
allDoc
);
}
else
{
count
-=
1
;
}
};
};
errCallback
=
function
(
err
)
{
if
(
err
.
status
===
404
)
{
//status
//statustext "Not Found"
//error
//reason "reason"
//message "did not work"
err
.
error
=
"
not_found
"
;
that
.
error
(
err
);
}
else
{
return
that
.
retry
(
err
);
}
};
i
=
resultTable
.
length
-
1
;
if
(
command
.
getOption
(
"
include_docs
"
)
===
true
)
{
for
(
i
;
i
>=
0
;
i
-=
1
)
{
keyId
=
resultTable
[
i
];
Signature
=
that
.
encodeAuthorization
(
keyId
);
callURL
=
'
http://
'
+
priv
.
server
+
'
.s3.amazonaws.com/
'
+
keyId
;
requestUTC
=
new
Date
().
toUTCString
();
parse
=
true
;
allDocResponse
.
rows
[
i
]
=
{
"
id
"
:
priv
.
fileNameToIds
(
keyId
).
join
(),
"
key
"
:
keyId
,
"
value
"
:
{}
};
checkCounter
=
i
;
$
.
ajax
({
contentType
:
''
,
crossdomain
:
true
,
url
:
callURL
,
type
:
'
GET
'
,
headers
:
{
'
Authorization
'
:
"
AWS
"
+
"
"
+
priv
.
AWSIdentifier
+
"
:
"
+
Signature
,
'
x-amz-date
'
:
requestUTC
,
'
Content-Type
'
:
'
application/json
'
//'Content-MD5' : ''
//'Content-Length' : ,
//'Expect' : ,
//'x-amz-security-token' : ,
},
success
:
dealCallback
(
i
,
countB
,
allDocResponse
),
error
:
errCallback
(
that
.
error
)
});
countB
+=
1
;
}
}
else
{
for
(
i
;
i
>=
0
;
i
-=
1
)
{
keyId
=
resultTable
[
i
];
allDocResponse
.
rows
[
i
]
=
{
"
id
"
:
priv
.
fileNameToIds
(
keyId
).
join
(),
"
key
"
:
keyId
,
"
value
"
:
{}
};
}
that
.
success
(
allDocResponse
);
}
}
function
getXML
()
{
//XHRwrapper(command,'PUT','text/plain; charset=UTF-8',true);
that
.
XHRwrapper
(
command
,
''
,
''
,
'
GET
'
,
mime
,
''
,
false
,
false
,
function
(
reponse
)
{
mon_document
=
reponse
;
makeJSON
();
}
);
}
getXML
();
//fin alldocs
};
return
that
;
});
}));
src/jio.storage/splitstorage.js
deleted
100644 → 0
View file @
3356688e
/*
* Copyright 2013, Nexedi SA
* Released under the LGPL license.
* http://www.gnu.org/licenses/lgpl.html
*/
/*jslint indent:2, maxlen: 80, nomen: true */
/*global jIO: true, exports: true, define: true */
/**
* Provides a split storage for JIO. This storage splits data
* and store them in the sub storages defined on the description.
*
* {
* "type": "split",
* "storage_list": [<storage description>, ...]
* }
*/
// define([module_name], [dependencies], module);
(
function
(
dependencies
,
module
)
{
"
use strict
"
;
if
(
typeof
define
===
'
function
'
&&
define
.
amd
)
{
return
define
(
dependencies
,
module
);
}
module
(
jIO
);
}([
'
jio
'
],
function
(
jIO
)
{
"
use strict
"
;
/**
* Generate a new uuid
*
* @method generateUuid
* @private
* @return {String} The new uuid
*/
function
generateUuid
()
{
function
S4
()
{
/* 65536 */
var
i
,
string
=
Math
.
floor
(
Math
.
random
()
*
0x10000
).
toString
(
16
);
for
(
i
=
string
.
length
;
i
<
4
;
i
+=
1
)
{
string
=
'
0
'
+
string
;
}
return
string
;
}
return
S4
()
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
S4
()
+
S4
();
}
/**
* Class to merge allDocs responses from several sub storages.
*
* @class AllDocsResponseMerger
* @constructor
*/
function
AllDocsResponseMerger
()
{
/**
* A list of allDocs response.
*
* @attribute response_list
* @type {Array} Contains allDocs responses
* @default []
*/
this
.
response_list
=
[];
}
AllDocsResponseMerger
.
prototype
.
constructor
=
AllDocsResponseMerger
;
/**
* Add an allDocs response to the response list.
*
* @method addResponse
* @param {Object} response The allDocs response.
* @return {AllDocsResponseMerger} This
*/
AllDocsResponseMerger
.
prototype
.
addResponse
=
function
(
response
)
{
this
.
response_list
.
push
(
response
);
return
this
;
};
/**
* Add several allDocs responses to the response list.
*
* @method addResponseList
* @param {Array} response_list An array of allDocs responses.
* @return {AllDocsResponseMerger} This
*/
AllDocsResponseMerger
.
prototype
.
addResponseList
=
function
(
response_list
)
{
var
i
;
for
(
i
=
0
;
i
<
response_list
.
length
;
i
+=
1
)
{
this
.
response_list
.
push
(
response_list
[
i
]);
}
return
this
;
};
/**
* Merge the response_list to one allDocs response.
*
* The merger will find rows with the same id in order to merge them, thanks
* to the onRowToMerge method. If no row correspond to an id, rows with the
* same id will be ignored.
*
* @method merge
* @param {Object} [option={}] The merge options
* @param {Boolean} [option.include_docs=false] Tell the merger to also
* merge metadata if true.
* @return {Object} The merged allDocs response.
*/
AllDocsResponseMerger
.
prototype
.
merge
=
function
(
option
)
{
var
result
=
[],
row
,
to_merge
=
[],
tmp
,
i
;
if
(
this
.
response_list
.
length
===
0
)
{
return
[];
}
while
((
row
=
this
.
response_list
[
0
].
rows
.
shift
())
!==
undefined
)
{
to_merge
[
0
]
=
row
;
for
(
i
=
1
;
i
<
this
.
response_list
.
length
;
i
+=
1
)
{
to_merge
[
i
]
=
AllDocsResponseMerger
.
listPopFromRowId
(
this
.
response_list
[
i
].
rows
,
row
.
id
);
if
(
to_merge
[
i
]
===
undefined
)
{
break
;
}
}
tmp
=
this
.
onRowToMerge
(
to_merge
,
option
||
{});
if
(
tmp
!==
undefined
)
{
result
[
result
.
length
]
=
tmp
;
}
}
this
.
response_list
=
[];
return
{
"
total_rows
"
:
result
.
length
,
"
rows
"
:
result
};
};
/**
* This method is called when the merger want to merge several rows with the
* same id.
*
* @method onRowToMerge
* @param {Array} row_list An array of rows.
* @param {Object} [option={}] The merge option.
* @param {Boolean} [option.include_docs=false] Also merge the metadata if
* true
* @return {Object} The merged row
*/
AllDocsResponseMerger
.
prototype
.
onRowToMerge
=
function
(
row_list
,
option
)
{
var
i
,
k
,
new_row
=
{
"
value
"
:
{}},
data
=
""
;
option
=
option
||
{};
for
(
i
=
0
;
i
<
row_list
.
length
;
i
+=
1
)
{
new_row
.
id
=
row_list
[
i
].
id
;
if
(
row_list
[
i
].
key
)
{
new_row
.
key
=
row_list
[
i
].
key
;
}
if
(
option
.
include_docs
)
{
new_row
.
doc
=
new_row
.
doc
||
{};
for
(
k
in
row_list
[
i
].
doc
)
{
if
(
row_list
[
i
].
doc
.
hasOwnProperty
(
k
))
{
if
(
k
[
0
]
===
"
_
"
)
{
new_row
.
doc
[
k
]
=
row_list
[
i
].
doc
[
k
];
}
}
}
data
+=
row_list
[
i
].
doc
.
data
;
}
}
if
(
option
.
include_docs
)
{
try
{
data
=
JSON
.
parse
(
data
);
}
catch
(
e
)
{
return
undefined
;
}
for
(
k
in
data
)
{
if
(
data
.
hasOwnProperty
(
k
))
{
new_row
.
doc
[
k
]
=
data
[
k
];
}
}
}
return
new_row
;
};
/**
* Search for a specific row and pop it. During the search operation, all
* parsed rows are stored on a dictionnary in order to be found instantly
* later.
*
* @method listPopFromRowId
* @param {Array} rows The row list
* @param {String} doc_id The document/row id
* @return {Object/undefined} The poped row
*/
AllDocsResponseMerger
.
listPopFromRowId
=
function
(
rows
,
doc_id
)
{
var
row
;
if
(
!
rows
.
dict
)
{
rows
.
dict
=
{};
}
if
(
rows
.
dict
[
doc_id
])
{
row
=
rows
.
dict
[
doc_id
];
delete
rows
.
dict
[
doc_id
];
return
row
;
}
while
((
row
=
rows
.
shift
())
!==
undefined
)
{
if
(
row
.
id
===
doc_id
)
{
return
row
;
}
rows
.
dict
[
row
.
id
]
=
row
;
}
};
/**
* The split storage class used by JIO.
*
* A split storage instance is able to i/o on several sub storages with
* split documents.
*
* @class splitStorage
*/
function
splitStorage
(
spec
,
my
)
{
var
that
=
my
.
basicStorage
(
spec
,
my
),
priv
=
{};
/**
* The list of sub storages we want to use to store part of documents.
*
* @attribute storage_list
* @private
* @type {Array} Array of storage descriptions
*/
priv
.
storage_list
=
spec
.
storage_list
;
//////////////////////////////////////////////////////////////////////
// Overrides
/**
* Overrides the original {{#crossLink "storage/specToStore:method"}}
* specToStore method{{/crossLink}}.
*
* @method specToStore
* @return {Object} The specificities to store
*/
that
.
specToStore
=
function
()
{
return
{
"
storage_list
"
:
priv
.
storage_list
};
};
/**
* TODO validateState
*/
//////////////////////////////////////////////////////////////////////
// Tools
/**
* Send a command to all sub storages. All the response are returned
* in a list. The index of the response correspond to the storage_list
* index. If an error occurs during operation, the callback is called with
* `callback(err, undefined)`. The response is given with
* `callback(undefined, response_list)`.
*
* `doc` is the document informations but can also be a list of dedicated
* document informations. In this case, each document is associated to one
* sub storage.
*
* @method send
* @private
* @param {String} method The command method
* @param {Object,Array} doc The document information to send to each sub
* storages or a list of dedicated document
* @param {Object} option The command option
* @param {Function} callback Called at the end
*/
priv
.
send
=
function
(
method
,
doc
,
option
,
callback
)
{
var
i
,
answer_list
=
[],
failed
=
false
;
function
onEnd
()
{
i
+=
1
;
if
(
i
===
priv
.
storage_list
.
length
)
{
callback
(
undefined
,
answer_list
);
}
}
function
onSuccess
(
i
)
{
return
function
(
response
)
{
if
(
!
failed
)
{
answer_list
[
i
]
=
response
;
}
onEnd
();
};
}
function
onError
(
i
)
{
return
function
(
err
)
{
if
(
!
failed
)
{
failed
=
true
;
err
.
index
=
i
;
callback
(
err
,
undefined
);
}
};
}
if
(
!
Array
.
isArray
(
doc
))
{
for
(
i
=
0
;
i
<
priv
.
storage_list
.
length
;
i
+=
1
)
{
that
.
addJob
(
method
,
priv
.
storage_list
[
i
],
doc
,
option
,
onSuccess
(
i
),
onError
(
i
)
);
}
}
else
{
for
(
i
=
0
;
i
<
priv
.
storage_list
.
length
;
i
+=
1
)
{
that
.
addJob
(
method
,
priv
.
storage_list
[
i
],
doc
[
i
],
option
,
onSuccess
(
i
),
onError
(
i
)
);
}
}
i
=
0
;
};
/**
* Split document metadata then store them to the sub storages.
*
* @method postOrPut
* @private
* @param {Object} doc A serialized document object
* @param {Object} option Command option properties
* @param {String} method The command method ('post' or 'put')
*/
priv
.
postOrPut
=
function
(
doc
,
option
,
method
)
{
var
i
,
data
,
doc_list
=
[],
doc_underscores
=
{};
if
(
!
doc
.
_id
)
{
doc
.
_id
=
generateUuid
();
}
for
(
i
in
doc
)
{
if
(
doc
.
hasOwnProperty
(
i
))
{
if
(
i
[
0
]
===
"
_
"
)
{
doc_underscores
[
i
]
=
doc
[
i
];
delete
doc
[
i
];
}
}
}
data
=
JSON
.
stringify
(
doc
);
for
(
i
=
0
;
i
<
priv
.
storage_list
.
length
;
i
+=
1
)
{
doc_list
[
i
]
=
JSON
.
parse
(
JSON
.
stringify
(
doc_underscores
));
doc_list
[
i
].
data
=
data
.
slice
(
(
data
.
length
/
priv
.
storage_list
.
length
)
*
i
,
(
data
.
length
/
priv
.
storage_list
.
length
)
*
(
i
+
1
)
);
}
priv
.
send
(
method
,
doc_list
,
option
,
function
(
err
,
response
)
{
if
(
err
)
{
err
.
message
=
"
Unable to
"
+
method
+
"
document
"
;
delete
err
.
index
;
return
that
.
error
(
err
);
}
that
.
success
({
"
ok
"
:
true
,
"
id
"
:
doc_underscores
.
_id
});
});
};
//////////////////////////////////////////////////////////////////////
// JIO commands
/**
* Split document metadata then store them to the sub storages.
*
* @method post
* @param {Command} command The JIO command
*/
that
.
post
=
function
(
command
)
{
priv
.
postOrPut
(
command
.
cloneDoc
(),
command
.
cloneOption
(),
'
post
'
);
};
/**
* Split document metadata then store them to the sub storages.
*
* @method put
* @param {Command} command The JIO command
*/
that
.
put
=
function
(
command
)
{
priv
.
postOrPut
(
command
.
cloneDoc
(),
command
.
cloneOption
(),
'
put
'
);
};
/**
* Puts an attachment to the sub storages.
*
* @method putAttachment
* @param {Command} command The JIO command
*/
that
.
putAttachment
=
function
(
command
)
{
var
i
,
attachment_list
=
[],
data
=
command
.
getAttachmentData
();
for
(
i
=
0
;
i
<
priv
.
storage_list
.
length
;
i
+=
1
)
{
attachment_list
[
i
]
=
command
.
cloneDoc
();
attachment_list
[
i
].
_data
=
data
.
slice
(
(
data
.
length
/
priv
.
storage_list
.
length
)
*
i
,
(
data
.
length
/
priv
.
storage_list
.
length
)
*
(
i
+
1
)
);
}
priv
.
send
(
'
putAttachment
'
,
attachment_list
,
command
.
cloneOption
(),
function
(
err
,
response
)
{
if
(
err
)
{
err
.
message
=
"
Unable to put attachment
"
;
delete
err
.
index
;
return
that
.
error
(
err
);
}
that
.
success
({
"
ok
"
:
true
,
"
id
"
:
command
.
getDocId
(),
"
attachment
"
:
command
.
getAttachmentId
()
});
}
);
};
/**
* Gets splited document metadata then returns real document.
*
* @method get
* @param {Command} command The JIO command
*/
that
.
get
=
function
(
command
)
{
var
doc
,
option
,
data
,
attachments
;
doc
=
command
.
cloneDoc
();
option
=
command
.
cloneOption
();
priv
.
send
(
'
get
'
,
doc
,
option
,
function
(
err
,
response
)
{
var
i
,
k
;
if
(
err
)
{
err
.
message
=
"
Unable to get document
"
;
delete
err
.
index
;
return
that
.
error
(
err
);
}
doc
=
''
;
for
(
i
=
0
;
i
<
response
.
length
;
i
+=
1
)
{
doc
+=
response
[
i
].
data
;
}
doc
=
JSON
.
parse
(
doc
);
for
(
i
=
0
;
i
<
response
.
length
;
i
+=
1
)
{
for
(
k
in
response
[
i
])
{
if
(
response
[
i
].
hasOwnProperty
(
k
))
{
if
(
k
[
0
]
===
"
_
"
)
{
doc
[
k
]
=
response
[
i
][
k
];
}
}
}
}
delete
doc
.
_attachments
;
for
(
i
=
0
;
i
<
response
.
length
;
i
+=
1
)
{
if
(
response
[
i
].
_attachments
)
{
for
(
k
in
response
[
i
].
_attachments
)
{
if
(
response
[
i
].
_attachments
.
hasOwnProperty
(
k
))
{
doc
.
_attachments
=
doc
.
_attachments
||
{};
doc
.
_attachments
[
k
]
=
doc
.
_attachments
[
k
]
||
{
"
length
"
:
0
,
"
content_type
"
:
""
};
doc
.
_attachments
[
k
].
length
+=
response
[
i
].
_attachments
[
k
].
length
;
// if (response[i]._attachments[k].digest) {
// if (doc._attachments[k].digest) {
// doc._attachments[k].digest += " " + response[i].
// _attachments[k].digest;
// } else {
// doc._attachments[k].digest = response[i].
// _attachments[k].digest;
// }
// }
doc
.
_attachments
[
k
].
content_type
=
response
[
i
].
_attachments
[
k
].
content_type
;
}
}
}
}
doc
.
_id
=
command
.
getDocId
();
that
.
success
(
doc
);
});
};
/**
* Gets splited document attachment then returns real attachment data.
*
* @method getAttachment
* @param {Command} command The JIO command
*/
that
.
getAttachment
=
function
(
command
)
{
var
doc
,
option
;
doc
=
command
.
cloneDoc
();
option
=
command
.
cloneOption
();
priv
.
send
(
'
getAttachment
'
,
doc
,
option
,
function
(
err
,
response
)
{
var
i
,
k
;
if
(
err
)
{
err
.
message
=
"
Unable to get attachment
"
;
delete
err
.
index
;
return
that
.
error
(
err
);
}
doc
=
''
;
for
(
i
=
0
;
i
<
response
.
length
;
i
+=
1
)
{
doc
+=
response
[
i
];
}
that
.
success
(
doc
);
});
};
/**
* Removes a document from the sub storages.
*
* @method remove
* @param {Command} command The JIO command
*/
that
.
remove
=
function
(
command
)
{
priv
.
send
(
'
remove
'
,
command
.
cloneDoc
(),
command
.
cloneOption
(),
function
(
err
,
response_list
)
{
if
(
err
)
{
err
.
message
=
"
Unable to remove document
"
;
delete
err
.
index
;
return
that
.
error
(
err
);
}
that
.
success
({
"
id
"
:
command
.
getDocId
(),
"
ok
"
:
true
});
}
);
};
/**
* Removes an attachment from the sub storages.
*
* @method removeAttachment
* @param {Command} command The JIO command
*/
that
.
removeAttachment
=
function
(
command
)
{
var
doc
=
command
.
cloneDoc
();
priv
.
send
(
'
removeAttachment
'
,
doc
,
command
.
cloneOption
(),
function
(
err
,
response_list
)
{
if
(
err
)
{
err
.
message
=
"
Unable to remove attachment
"
;
delete
err
.
index
;
return
that
.
error
(
err
);
}
that
.
success
({
"
id
"
:
doc
.
_id
,
"
attachment
"
:
doc
.
_attachment
,
"
ok
"
:
true
});
}
);
};
/**
* Retreive a list of all document in the sub storages.
*
* If include_docs option is false, then it returns the document list from
* the first sub storage. Else, it will merge results and return.
*
* @method allDocs
* @param {Command} command The JIO command
*/
that
.
allDocs
=
function
(
command
)
{
var
option
=
command
.
cloneOption
();
option
=
{
"
include_docs
"
:
option
.
include_docs
};
priv
.
send
(
'
allDocs
'
,
command
.
cloneDoc
(),
option
,
function
(
err
,
response_list
)
{
var
all_docs_merger
;
if
(
err
)
{
err
.
message
=
"
Unable to retrieve document list
"
;
delete
err
.
index
;
return
that
.
error
(
err
);
}
all_docs_merger
=
new
AllDocsResponseMerger
();
all_docs_merger
.
addResponseList
(
response_list
);
return
that
.
success
(
all_docs_merger
.
merge
(
option
));
}
);
};
return
that
;
}
// end of splitStorage
jIO
.
addStorageType
(
'
split
'
,
splitStorage
);
}));
src/jio.storage/xwikistorage.js
deleted
100644 → 0
View file @
3356688e
/*jslint indent: 2,
maxlen: 80,
nomen: true
*/
/*global
define: true,
jIO: true,
jQuery: true,
XMLHttpRequest: true,
Blob: true,
FormData: true,
window: true
*/
/**
* JIO XWiki Storage. Type = 'xwiki'.
* XWiki Document/Attachment storage.
*/
(
function
()
{
"
use strict
"
;
var
$
,
store
;
store
=
function
(
spec
,
my
)
{
spec
=
spec
||
{};
var
that
,
priv
,
xwikistorage
;
that
=
my
.
basicStorage
(
spec
,
my
);
priv
=
{};
/**
* Get the Space and Page components of a documkent ID.
*
* @param id the document id.
* @return a map of { 'space':<Space>, 'page':<Page> }
*/
priv
.
getParts
=
function
(
id
)
{
if
(
id
.
indexOf
(
'
/
'
)
===
-
1
)
{
return
{
space
:
'
Main
'
,
page
:
id
};
}
return
{
space
:
id
.
substring
(
0
,
id
.
indexOf
(
'
/
'
)),
page
:
id
.
substring
(
id
.
indexOf
(
'
/
'
)
+
1
)
};
};
/**
* Get the Anti-CSRF token and do something with it.
*
* @param andThen function which is called with (formToken, err)
* as parameters.
*/
priv
.
doWithFormToken
=
function
(
andThen
)
{
$
.
ajax
({
url
:
priv
.
formTokenPath
,
type
:
"
GET
"
,
async
:
true
,
dataType
:
'
text
'
,
success
:
function
(
html
)
{
var
m
,
token
;
// this is unreliable
//var token = $('meta[name=form_token]', html).attr("content");
m
=
html
.
match
(
/<meta name="form_token" content="
(\w
*
)
"
\/
>/
);
token
=
(
m
&&
m
[
1
])
||
null
;
if
(
!
token
)
{
andThen
(
null
,
{
"
status
"
:
404
,
"
statusText
"
:
"
Not Found
"
,
"
error
"
:
"
err_form_token_not_found
"
,
"
message
"
:
"
Anti-CSRF form token was not found in page
"
,
"
reason
"
:
"
XWiki main page did not contain expected
"
+
"
Anti-CSRF form token
"
});
}
else
{
andThen
(
token
,
null
);
}
},
error
:
function
(
jqxhr
,
err
,
cause
)
{
andThen
(
null
,
{
"
status
"
:
jqxhr
.
status
,
"
statusText
"
:
jqxhr
.
statusText
,
"
error
"
:
err
,
"
message
"
:
"
Could not get Anti-CSRF form token from [
"
+
priv
.
xwikiurl
+
"
]
"
,
"
reason
"
:
cause
});
},
});
};
/**
* Get the REST read URL for a document.
*
* @param docId the id of the document.
* @return the REST URL for accessing this document.
*/
priv
.
getDocRestURL
=
function
(
docId
)
{
var
parts
=
priv
.
getParts
(
docId
);
return
priv
.
xwikiurl
+
'
/rest/wikis/
'
+
priv
.
wiki
+
'
/spaces/
'
+
parts
.
space
+
'
/pages/
'
+
parts
.
page
;
};
/**
* Make an HTML5 Blob object.
* Equivilant to the `new Blob()` constructor.
* Will fall back on deprecated BlobBuilder if necessary.
*/
priv
.
makeBlob
=
function
(
contentArray
,
options
)
{
var
i
,
bb
,
BB
;
try
{
// use the constructor if possible.
return
new
Blob
(
contentArray
,
options
);
}
catch
(
err
)
{
// fall back on the blob builder.
BB
=
(
window
.
MozBlobBuilder
||
window
.
WebKitBlobBuilder
||
window
.
BlobBuilder
);
bb
=
new
BB
();
for
(
i
=
0
;
i
<
contentArray
.
length
;
i
+=
1
)
{
bb
.
append
(
contentArray
[
i
]);
}
return
bb
.
getBlob
(
options
?
options
.
type
:
undefined
);
}
};
priv
.
isBlob
=
function
(
potentialBlob
)
{
return
typeof
(
potentialBlob
)
!==
'
undefined
'
&&
potentialBlob
.
toString
()
===
"
[object Blob]
"
;
};
/*
* Wrapper for the xwikistorage based on localstorage JiO store.
*/
xwikistorage
=
{
/**
* Get content of an XWikiDocument.
*
* @param docId the document ID.
* @param andThen a callback taking (doc, err), doc being the document
* json object and err being the error if any.
*/
getItem
:
function
(
docId
,
andThen
)
{
var
success
=
function
(
jqxhr
)
{
var
out
,
xd
;
out
=
{};
try
{
xd
=
$
(
jqxhr
.
responseText
);
xd
.
find
(
'
modified
'
).
each
(
function
()
{
out
.
_last_modified
=
Date
.
parse
(
$
(
this
).
text
());
});
xd
.
find
(
'
created
'
).
each
(
function
()
{
out
.
_creation_date
=
Date
.
parse
(
$
(
this
).
text
());
});
xd
.
find
(
'
title
'
).
each
(
function
()
{
out
.
title
=
$
(
this
).
text
();
});
xd
.
find
(
'
parent
'
).
each
(
function
()
{
out
.
parent
=
$
(
this
).
text
();
});
xd
.
find
(
'
syntax
'
).
each
(
function
()
{
out
.
syntax
=
$
(
this
).
text
();
});
xd
.
find
(
'
content
'
).
each
(
function
()
{
out
.
content
=
$
(
this
).
text
();
});
out
.
_id
=
docId
;
andThen
(
out
,
null
);
}
catch
(
err
)
{
andThen
(
null
,
{
status
:
500
,
statusText
:
"
internal error
"
,
error
:
err
,
message
:
err
.
message
,
reason
:
""
});
}
};
$
.
ajax
({
url
:
priv
.
getDocRestURL
(
docId
),
type
:
"
GET
"
,
async
:
true
,
dataType
:
'
xml
'
,
// Use complete instead of success and error because phantomjs
// sometimes causes error to be called with html return code 200.
complete
:
function
(
jqxhr
)
{
if
(
jqxhr
.
status
===
404
)
{
andThen
(
null
,
null
);
return
;
}
if
(
jqxhr
.
status
!==
200
)
{
andThen
(
null
,
{
"
status
"
:
jqxhr
.
status
,
"
statusText
"
:
jqxhr
.
statusText
,
"
error
"
:
""
,
"
message
"
:
"
Failed to get document [
"
+
docId
+
"
]
"
,
"
reason
"
:
""
});
return
;
}
success
(
jqxhr
);
}
});
},
/**
* Get content of an XWikiAttachment.
*
* @param attachId the attachment ID.
* @param andThen a callback taking (attach, err), attach being the
* attachment blob and err being the error if any.
*/
getAttachment
:
function
(
docId
,
fileName
,
andThen
)
{
var
xhr
,
parts
,
url
;
// need to do this manually, jquery doesn't support returning blobs.
xhr
=
new
XMLHttpRequest
();
parts
=
priv
.
getParts
(
docId
);
url
=
priv
.
xwikiurl
+
'
/bin/download/
'
+
parts
.
space
+
"
/
"
+
parts
.
page
+
"
/
"
+
fileName
+
'
?cb=
'
+
Math
.
random
();
xhr
.
open
(
'
GET
'
,
url
,
true
);
if
(
priv
.
useBlobs
)
{
xhr
.
responseType
=
'
blob
'
;
}
else
{
xhr
.
responseType
=
'
text
'
;
}
xhr
.
onload
=
function
(
e
)
{
if
(
xhr
.
status
===
200
)
{
var
contentType
=
xhr
.
getResponseHeader
(
"
Content-Type
"
);
if
(
contentType
.
indexOf
(
'
;
'
)
>
-
1
)
{
contentType
=
contentType
.
substring
(
0
,
contentType
.
indexOf
(
'
;
'
));
}
andThen
(
xhr
.
response
);
}
else
{
andThen
(
null
,
{
"
status
"
:
xhr
.
status
,
"
statusText
"
:
xhr
.
statusText
,
"
error
"
:
"
err_network_error
"
,
"
message
"
:
"
Failed to get attachment [
"
+
docId
+
"
/
"
+
fileName
+
"
]
"
,
"
reason
"
:
"
Error getting data from network
"
});
}
};
xhr
.
send
();
},
/**
* Store an XWikiDocument.
*
* @param id the document identifier.
* @param doc the document JSON object containing
* "parent", "title", "content", and/or "syntax" keys.
* @param andThen a callback taking (err), err being the error if any.
*/
setItem
:
function
(
id
,
doc
,
andThen
)
{
priv
.
doWithFormToken
(
function
(
formToken
,
err
)
{
if
(
err
)
{
that
.
error
(
err
);
return
;
}
var
parts
=
priv
.
getParts
(
id
);
$
.
ajax
({
url
:
priv
.
xwikiurl
+
"
/bin/preview/
"
+
parts
.
space
+
'
/
'
+
parts
.
page
,
type
:
"
POST
"
,
async
:
true
,
dataType
:
'
text
'
,
data
:
{
parent
:
doc
.
parent
||
''
,
title
:
doc
.
title
||
''
,
xredirect
:
''
,
language
:
'
en
'
,
// RequiresHTMLConversion: 'content',
// content_syntax: doc.syntax || 'xwiki/2.1',
content
:
doc
.
content
||
''
,
xeditaction
:
'
edit
'
,
comment
:
'
Saved by JiO
'
,
action_saveandcontinue
:
'
Save & Continue
'
,
syntaxId
:
doc
.
syntax
||
'
xwiki/2.1
'
,
xhidden
:
0
,
minorEdit
:
0
,
ajax
:
true
,
form_token
:
formToken
},
success
:
function
()
{
andThen
(
null
);
},
error
:
function
(
jqxhr
,
err
,
cause
)
{
andThen
({
"
status
"
:
jqxhr
.
status
,
"
statusText
"
:
jqxhr
.
statusText
,
"
error
"
:
err
,
"
message
"
:
"
Failed to store document [
"
+
id
+
"
]
"
,
"
reason
"
:
cause
});
}
});
});
},
/**
* Store an XWikiAttachment.
*
* @param docId the ID of the document to attach to.
* @param fileName the attachment file name.
* @param mimeType the MIME type of the attachment content.
* @param content the attachment content.
* @param andThen a callback taking one parameter, the error if any.
*/
setAttachment
:
function
(
docId
,
fileName
,
mimeType
,
content
,
andThen
)
{
priv
.
doWithFormToken
(
function
(
formToken
,
err
)
{
var
parts
,
blob
,
fd
,
xhr
;
if
(
err
)
{
that
.
error
(
err
);
return
;
}
parts
=
priv
.
getParts
(
docId
);
blob
=
priv
.
isBlob
(
content
)
?
content
:
priv
.
makeBlob
([
content
],
{
type
:
mimeType
});
fd
=
new
FormData
();
fd
.
append
(
"
filepath
"
,
blob
,
fileName
);
fd
.
append
(
"
form_token
"
,
formToken
);
xhr
=
new
XMLHttpRequest
();
xhr
.
open
(
'
POST
'
,
priv
.
xwikiurl
+
"
/bin/upload/
"
+
parts
.
space
+
'
/
'
+
parts
.
page
,
true
);
xhr
.
onload
=
function
(
e
)
{
if
(
xhr
.
status
===
302
||
xhr
.
status
===
200
)
{
andThen
(
null
);
}
else
{
andThen
({
"
status
"
:
xhr
.
status
,
"
statusText
"
:
xhr
.
statusText
,
"
error
"
:
"
err_network_error
"
,
"
message
"
:
"
Failed to store attachment [
"
+
docId
+
"
/
"
+
fileName
+
"
]
"
,
"
reason
"
:
"
Error posting data
"
});
}
};
xhr
.
send
(
fd
);
});
},
removeItem
:
function
(
id
,
andThen
)
{
priv
.
doWithFormToken
(
function
(
formToken
,
err
)
{
if
(
err
)
{
that
.
error
(
err
);
return
;
}
var
parts
=
priv
.
getParts
(
id
);
$
.
ajax
({
url
:
priv
.
xwikiurl
+
"
/bin/delete/
"
+
parts
.
space
+
'
/
'
+
parts
.
page
,
type
:
"
POST
"
,
async
:
true
,
dataType
:
'
text
'
,
data
:
{
confirm
:
'
1
'
,
form_token
:
formToken
},
success
:
function
()
{
andThen
(
null
);
},
error
:
function
(
jqxhr
,
err
,
cause
)
{
andThen
({
"
status
"
:
jqxhr
.
status
,
"
statusText
"
:
jqxhr
.
statusText
,
"
error
"
:
err
,
"
message
"
:
"
Failed to delete document [
"
+
id
+
"
]
"
,
"
reason
"
:
cause
});
}
});
});
},
removeAttachment
:
function
(
docId
,
fileName
,
andThen
)
{
var
parts
=
priv
.
getParts
(
docId
);
priv
.
doWithFormToken
(
function
(
formToken
,
err
)
{
if
(
err
)
{
that
.
error
(
err
);
return
;
}
$
.
ajax
({
url
:
priv
.
xwikiurl
+
"
/bin/delattachment/
"
+
parts
.
space
+
'
/
'
+
parts
.
page
+
'
/
'
+
fileName
,
type
:
"
POST
"
,
async
:
true
,
dataType
:
'
text
'
,
data
:
{
ajax
:
'
1
'
,
form_token
:
formToken
},
success
:
function
()
{
andThen
(
null
);
},
error
:
function
(
jqxhr
,
err
,
cause
)
{
andThen
({
"
status
"
:
jqxhr
.
status
,
"
statusText
"
:
jqxhr
.
statusText
,
"
error
"
:
err
,
"
message
"
:
"
Failed to delete attachment [
"
+
docId
+
'
/
'
+
fileName
+
"
]
"
,
"
reason
"
:
cause
});
}
});
});
}
};
// ==================== Tools ====================
/**
* Update [doc] the document object and remove [doc] keys
* which are not in [new_doc]. It only changes [doc] keys not starting
* with an underscore.
* ex: doc: {key:value1,_key:value2} with
* new_doc: {key:value3,_key:value4} updates
* doc: {key:value3,_key:value2}.
* @param {object} doc The original document object.
* @param {object} new_doc The new document object
*/
priv
.
documentObjectUpdate
=
function
(
doc
,
new_doc
)
{
var
k
;
for
(
k
in
doc
)
{
if
(
doc
.
hasOwnProperty
(
k
))
{
if
(
k
[
0
]
!==
'
_
'
)
{
delete
doc
[
k
];
}
}
}
for
(
k
in
new_doc
)
{
if
(
new_doc
.
hasOwnProperty
(
k
))
{
if
(
k
[
0
]
!==
'
_
'
)
{
doc
[
k
]
=
new_doc
[
k
];
}
}
}
};
/**
* Checks if an object has no enumerable keys
* @method objectIsEmpty
* @param {object} obj The object
* @return {boolean} true if empty, else false
*/
priv
.
objectIsEmpty
=
function
(
obj
)
{
var
k
;
for
(
k
in
obj
)
{
if
(
obj
.
hasOwnProperty
(
k
))
{
return
false
;
}
}
return
true
;
};
// ==================== attributes ====================
// the wiki to store stuff in
priv
.
wiki
=
spec
.
wiki
||
'
xwiki
'
;
// unused
priv
.
username
=
spec
.
username
;
priv
.
language
=
spec
.
language
;
// URL location of the wiki, unused since
// XWiki doesn't currently allow cross-domain requests.
priv
.
xwikiurl
=
spec
.
xwikiurl
||
window
.
location
.
href
.
replace
(
/
\/
xwiki
\/
bin
\/
/
,
'
/xwiki
\n
'
)
.
split
(
'
\n
'
)[
0
];
// should be: s@/xwiki/bin/.*$@/xwiki@
// but jslint gets in the way.
// Which URL to load for getting the Anti-CSRF form token, used for testing.
priv
.
formTokenPath
=
spec
.
formTokenPath
||
priv
.
xwikiurl
;
// If true then Blob objects will be returned by
// getAttachment() rather than strings.
priv
.
useBlobs
=
spec
.
useBlobs
||
false
;
// If true then Blob objects will be returned by
// getAttachment() rather than strings.
priv
.
useBlobs
=
spec
.
useBlobs
||
false
;
that
.
specToStore
=
function
()
{
return
{
"
username
"
:
priv
.
username
,
"
language
"
:
priv
.
language
,
"
xwikiurl
"
:
priv
.
xwikiurl
,
};
};
// can't fo wrong since no parameters are required.
that
.
validateState
=
function
()
{
return
''
;
};
// ==================== commands ====================
/**
* Create a document in local storage.
* @method post
* @param {object} command The JIO command
*/
that
.
post
=
function
(
command
)
{
var
docId
=
command
.
getDocId
();
if
(
!
(
typeof
docId
===
"
string
"
&&
docId
!==
""
))
{
setTimeout
(
function
()
{
that
.
error
({
"
status
"
:
405
,
"
statusText
"
:
"
Method Not Allowed
"
,
"
error
"
:
"
method_not_allowed
"
,
"
message
"
:
"
Cannot create document which id is undefined
"
,
"
reason
"
:
"
Document id is undefined
"
});
});
return
;
}
xwikistorage
.
getItem
(
docId
,
function
(
doc
,
err
)
{
if
(
err
)
{
that
.
error
(
err
);
}
else
if
(
doc
===
null
)
{
// the document does not exist
xwikistorage
.
setItem
(
command
.
getDocId
(),
command
.
cloneDoc
(),
function
(
err
)
{
if
(
err
)
{
that
.
error
(
err
);
}
else
{
that
.
success
({
"
ok
"
:
true
,
"
id
"
:
command
.
getDocId
()
});
}
});
}
else
{
// the document already exists
that
.
error
({
"
status
"
:
409
,
"
statusText
"
:
"
Conflicts
"
,
"
error
"
:
"
conflicts
"
,
"
message
"
:
"
Cannot create a new document
"
,
"
reason
"
:
"
Document already exists (use 'put' to modify it)
"
});
}
});
};
/**
* Create or update a document in local storage.
* @method put
* @param {object} command The JIO command
*/
that
.
put
=
function
(
command
)
{
xwikistorage
.
getItem
(
command
.
getDocId
(),
function
(
doc
,
err
)
{
if
(
err
)
{
that
.
error
(
err
);
}
else
if
(
doc
===
null
)
{
doc
=
command
.
cloneDoc
();
}
else
{
priv
.
documentObjectUpdate
(
doc
,
command
.
cloneDoc
());
}
// write
xwikistorage
.
setItem
(
command
.
getDocId
(),
doc
,
function
(
err
)
{
if
(
err
)
{
that
.
error
(
err
);
}
else
{
that
.
success
({
"
ok
"
:
true
,
"
id
"
:
command
.
getDocId
()
});
}
});
});
};
/**
* Add an attachment to a document
* @method putAttachment
* @param {object} command The JIO command
*/
that
.
putAttachment
=
function
(
command
)
{
xwikistorage
.
getItem
(
command
.
getDocId
(),
function
(
doc
,
err
)
{
if
(
err
)
{
that
.
error
(
err
);
}
else
if
(
doc
===
null
)
{
// the document does not exist
that
.
error
({
"
status
"
:
404
,
"
statusText
"
:
"
Not Found
"
,
"
error
"
:
"
not_found
"
,
"
message
"
:
"
Impossible to add attachment
"
,
"
reason
"
:
"
Document not found
"
});
}
else
{
// Document exists, upload attachment.
xwikistorage
.
setAttachment
(
command
.
getDocId
(),
command
.
getAttachmentId
(),
command
.
getAttachmentMimeType
(),
command
.
getAttachmentData
(),
function
(
err
)
{
if
(
err
)
{
that
.
error
(
err
);
}
else
{
that
.
success
({
"
ok
"
:
true
,
"
id
"
:
command
.
getDocId
()
+
"
/
"
+
command
.
getAttachmentId
()
});
}
});
}
});
};
/**
* Get a document or attachment
* @method get
* @param {object} command The JIO command
*/
that
.
get
=
that
.
getAttachment
=
function
(
command
)
{
if
(
typeof
command
.
getAttachmentId
()
===
"
string
"
)
{
// seeking for an attachment
xwikistorage
.
getAttachment
(
command
.
getDocId
(),
command
.
getAttachmentId
(),
function
(
attach
,
err
)
{
if
(
err
)
{
that
.
error
(
err
);
}
else
if
(
attach
!==
null
)
{
that
.
success
(
attach
);
}
else
{
that
.
error
({
"
status
"
:
404
,
"
statusText
"
:
"
Not Found
"
,
"
error
"
:
"
not_found
"
,
"
message
"
:
"
Cannot find the attachment
"
,
"
reason
"
:
"
Attachment does not exist
"
});
}
});
}
else
{
// seeking for a document
xwikistorage
.
getItem
(
command
.
getDocId
(),
function
(
doc
,
err
)
{
if
(
err
)
{
that
.
error
(
err
);
}
else
if
(
doc
!==
null
)
{
that
.
success
(
doc
);
}
else
{
that
.
error
({
"
status
"
:
404
,
"
statusText
"
:
"
Not Found
"
,
"
error
"
:
"
not_found
"
,
"
message
"
:
"
Cannot find the document
"
,
"
reason
"
:
"
Document does not exist
"
});
}
});
}
};
/**
* Remove a document or attachment
* @method remove
* @param {object} command The JIO command
*/
that
.
remove
=
that
.
removeAttachment
=
function
(
command
)
{
var
notFoundError
,
objId
,
complete
;
notFoundError
=
function
(
word
)
{
that
.
error
({
"
status
"
:
404
,
"
statusText
"
:
"
Not Found
"
,
"
error
"
:
"
not_found
"
,
"
message
"
:
word
+
"
not found
"
,
"
reason
"
:
"
missing
"
});
};
objId
=
command
.
getDocId
();
complete
=
function
(
err
)
{
if
(
err
)
{
that
.
error
(
err
);
}
else
{
that
.
success
({
"
ok
"
:
true
,
"
id
"
:
objId
});
}
};
if
(
typeof
command
.
getAttachmentId
()
===
"
string
"
)
{
objId
+=
'
/
'
+
command
.
getAttachmentId
();
xwikistorage
.
removeAttachment
(
command
.
getDocId
(),
command
.
getAttachmentId
(),
complete
);
}
else
{
xwikistorage
.
removeItem
(
objId
,
complete
);
}
};
/**
* Get all filenames belonging to a user from the document index
* @method allDocs
* @param {object} command The JIO command
*/
that
.
allDocs
=
function
()
{
setTimeout
(
function
()
{
that
.
error
({
"
status
"
:
405
,
"
statusText
"
:
"
Method Not Allowed
"
,
"
error
"
:
"
method_not_allowed
"
,
"
message
"
:
"
Your are not allowed to use this command
"
,
"
reason
"
:
"
xwikistorage forbids AllDocs command executions
"
});
});
};
return
that
;
};
if
(
typeof
(
define
)
===
'
function
'
&&
define
.
amd
)
{
define
([
'
jquery
'
,
'
jio
'
],
function
(
jquery
,
jIO
)
{
$
=
jquery
;
jIO
.
addStorageType
(
'
xwiki
'
,
store
);
});
}
else
{
jIO
.
addStorageType
(
'
xwiki
'
,
store
);
$
=
jQuery
;
}
}());
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment