├── README.md ├── css ├── bootstrap.css ├── favicon.ico ├── isdc-theme.css └── logo.png ├── data.sql ├── index.php ├── sphinx ├── bin │ ├── 1cmd.bat │ ├── 1start.bat │ ├── 1索引.bat │ ├── indexer.exe │ ├── indextool.exe │ ├── libeay32.dll │ ├── libiconv-2.dll │ ├── libintl-8.dll │ ├── libmariadb.dll │ ├── libpq.dll │ ├── msvcr120.dll │ ├── searchd.exe │ ├── sphinx.conf │ ├── ssleay32.dll │ └── wordbreaker.exe ├── etc │ ├── example.sql │ ├── sphinx-min.conf.dist │ ├── sphinx.conf.dist │ ├── uni.lib │ └── unigram.txt ├── misc │ ├── raminfo.py │ ├── resolve.py │ ├── search_json_pretty.php │ ├── searchd │ ├── suggest │ │ ├── README │ │ ├── suggest.conf │ │ └── suggest.php │ └── wordbreak.pl └── src │ ├── sphinxudf.c │ ├── sphinxudf.h │ └── udfexample.c ├── sphinxapi.php └── test.jpg /README.md: -------------------------------------------------------------------------------- 1 | # findpass 2 | 基于Sphinx的社工库 3 | 4 | 环境:Php + Mysql + Sphinx 5 | 6 | ![如图](/test.jpg) 7 | -------------------------------------------------------------------------------- /css/bootstrap.css: -------------------------------------------------------------------------------- 1 | 2 | article, 3 | aside, 4 | details, 5 | figcaption, 6 | figure, 7 | footer, 8 | header, 9 | hgroup, 10 | main, 11 | nav, 12 | section, 13 | summary { 14 | display: block; 15 | } 16 | 17 | 18 | html { 19 | font-family: sans-serif; 20 | -webkit-text-size-adjust: 100%; 21 | -ms-text-size-adjust: 100%; 22 | } 23 | 24 | body { 25 | margin: 0; 26 | } 27 | 28 | a { 29 | background: transparent; 30 | } 31 | 32 | a:focus { 33 | outline: thin dotted; 34 | } 35 | 36 | a:active, 37 | a:hover { 38 | outline: 0; 39 | } 40 | 41 | h1 { 42 | margin: 0.67em 0; 43 | font-size: 2em; 44 | } 45 | 46 | abbr[title] { 47 | border-bottom: 1px dotted; 48 | } 49 | 50 | b, 51 | strong { 52 | font-weight: bold; 53 | } 54 | 55 | 56 | hr { 57 | height: 0; 58 | -moz-box-sizing: content-box; 59 | box-sizing: content-box; 60 | } 61 | 62 | img { 63 | border: 0; 64 | } 65 | 66 | legend { 67 | padding: 0; 68 | border: 0; 69 | } 70 | 71 | button, 72 | input, 73 | select, 74 | textarea { 75 | margin: 0; 76 | font-family: inherit; 77 | font-size: 100%; 78 | } 79 | 80 | button, 81 | input { 82 | line-height: normal; 83 | } 84 | 85 | button, 86 | select { 87 | text-transform: none; 88 | } 89 | 90 | button, 91 | html input[type="button"], 92 | input[type="reset"], 93 | input[type="submit"] { 94 | cursor: pointer; 95 | -webkit-appearance: button; 96 | } 97 | 98 | button[disabled], 99 | html input[disabled] { 100 | cursor: default; 101 | } 102 | 103 | input[type="checkbox"], 104 | input[type="radio"] { 105 | padding: 0; 106 | box-sizing: border-box; 107 | } 108 | 109 | input[type="search"] { 110 | -webkit-box-sizing: content-box; 111 | -moz-box-sizing: content-box; 112 | box-sizing: content-box; 113 | -webkit-appearance: textfield; 114 | } 115 | 116 | input[type="search"]::-webkit-search-cancel-button, 117 | input[type="search"]::-webkit-search-decoration { 118 | -webkit-appearance: none; 119 | } 120 | 121 | button::-moz-focus-inner, 122 | input::-moz-focus-inner { 123 | padding: 0; 124 | border: 0; 125 | } 126 | 127 | textarea { 128 | overflow: auto; 129 | vertical-align: top; 130 | } 131 | 132 | table { 133 | border-collapse: collapse; 134 | border-spacing: 0; 135 | } 136 | 137 | @media print { 138 | * { 139 | color: #000 !important; 140 | text-shadow: none !important; 141 | background: transparent !important; 142 | box-shadow: none !important; 143 | } 144 | a, 145 | a:visited { 146 | text-decoration: underline; 147 | } 148 | a[href]:after { 149 | content: " (" attr(href) ")"; 150 | } 151 | abbr[title]:after { 152 | content: " (" attr(title) ")"; 153 | } 154 | a[href^="javascript:"]:after, 155 | a[href^="#"]:after { 156 | content: ""; 157 | } 158 | tr, 159 | img { 160 | page-break-inside: avoid; 161 | } 162 | img { 163 | max-width: 100% !important; 164 | } 165 | @page { 166 | margin: 2cm .5cm; 167 | } 168 | p, 169 | h2, 170 | h3 { 171 | orphans: 3; 172 | widows: 3; 173 | } 174 | h2, 175 | h3 { 176 | page-break-after: avoid; 177 | } 178 | select { 179 | background: #fff !important; 180 | } 181 | .navbar { 182 | display: none; 183 | } 184 | .table td, 185 | .table th { 186 | background-color: #fff !important; 187 | } 188 | .btn > .caret, 189 | .dropup > .btn > .caret { 190 | border-top-color: #000 !important; 191 | } 192 | .label { 193 | border: 1px solid #000; 194 | } 195 | .table { 196 | border-collapse: collapse !important; 197 | } 198 | .table-bordered th, 199 | .table-bordered td { 200 | border: 1px solid #ddd !important; 201 | } 202 | } 203 | 204 | *, 205 | *:before, 206 | *:after { 207 | -webkit-box-sizing: border-box; 208 | -moz-box-sizing: border-box; 209 | box-sizing: border-box; 210 | } 211 | 212 | html { 213 | font-size: 62.5%; 214 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 215 | } 216 | 217 | body { 218 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 219 | font-size: 14px; 220 | line-height: 1.428571429; 221 | color: #333333; 222 | background-color: #ffffff; 223 | } 224 | 225 | input, 226 | button, 227 | select, 228 | textarea { 229 | font-family: inherit; 230 | font-size: inherit; 231 | line-height: inherit; 232 | } 233 | 234 | a { 235 | color: #680808; 236 | text-decoration: none; 237 | } 238 | 239 | a:hover, 240 | a:focus { 241 | color: #2a6496; 242 | text-decoration: underline; 243 | } 244 | 245 | a:focus { 246 | outline: thin dotted; 247 | outline: 5px auto -webkit-focus-ring-color; 248 | outline-offset: -2px; 249 | } 250 | 251 | 252 | 253 | p { 254 | margin: 0 0 10px; 255 | } 256 | 257 | 258 | .text-muted { 259 | color: #999999; 260 | } 261 | 262 | .text-center { 263 | text-align: center; 264 | } 265 | 266 | .container { 267 | padding-right: 15px; 268 | padding-left: 15px; 269 | margin-right: auto; 270 | margin-left: auto; 271 | } 272 | 273 | .container:before, 274 | .container:after { 275 | display: table; 276 | content: " "; 277 | } 278 | 279 | .container:after { 280 | clear: both; 281 | } 282 | 283 | .container:before, 284 | .container:after { 285 | display: table; 286 | content: " "; 287 | } 288 | 289 | .container:after { 290 | clear: both; 291 | } 292 | 293 | 294 | @media (min-width: 768px) { 295 | .container { 296 | width: 750px; 297 | } 298 | } 299 | 300 | @media (min-width: 992px) { 301 | .container { 302 | width: 970px; 303 | } 304 | } 305 | 306 | @media (min-width: 1200px) { 307 | .container { 308 | width: 1170px; 309 | } 310 | } 311 | 312 | .row { 313 | margin-right: -15px; 314 | margin-left: -15px; 315 | } 316 | 317 | .row:before, 318 | .row:after { 319 | display: table; 320 | content: " "; 321 | } 322 | 323 | .row:after { 324 | clear: both; 325 | } 326 | 327 | .row:before, 328 | .row:after { 329 | display: table; 330 | content: " "; 331 | } 332 | 333 | .row:after { 334 | clear: both; 335 | } 336 | 337 | 338 | table { 339 | max-width: 100%; 340 | background-color: transparent; 341 | } 342 | 343 | th { 344 | text-align: left; 345 | } 346 | 347 | 348 | input[type="search"] { 349 | -webkit-box-sizing: border-box; 350 | -moz-box-sizing: border-box; 351 | box-sizing: border-box; 352 | } 353 | 354 | input[type="radio"], 355 | input[type="checkbox"] { 356 | margin: 4px 0 0; 357 | margin-top: 1px \9; 358 | /* IE8-9 */ 359 | 360 | line-height: normal; 361 | } 362 | 363 | input[type="file"] { 364 | display: block; 365 | } 366 | 367 | select[multiple], 368 | select[size] { 369 | height: auto; 370 | } 371 | 372 | select optgroup { 373 | font-family: inherit; 374 | font-size: inherit; 375 | font-style: inherit; 376 | } 377 | 378 | input[type="file"]:focus, 379 | input[type="radio"]:focus, 380 | input[type="checkbox"]:focus { 381 | outline: thin dotted; 382 | outline: 5px auto -webkit-focus-ring-color; 383 | outline-offset: -2px; 384 | } 385 | 386 | input[type="number"]::-webkit-outer-spin-button, 387 | input[type="number"]::-webkit-inner-spin-button { 388 | height: auto; 389 | } 390 | 391 | output { 392 | display: block; 393 | padding-top: 7px; 394 | font-size: 14px; 395 | line-height: 1.428571429; 396 | color: #555555; 397 | vertical-align: middle; 398 | } 399 | 400 | .form-control { 401 | display: block; 402 | width: 100%; 403 | height: 34px; 404 | padding: 6px 12px; 405 | font-size: 14px; 406 | line-height: 1.428571429; 407 | color: #555555; 408 | vertical-align: middle; 409 | background-color: #ffffff; 410 | background-image: none; 411 | border: 1px solid #cccccc; 412 | border-radius: 4px; 413 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 414 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 415 | -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; 416 | transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; 417 | } 418 | 419 | .form-control:focus { 420 | border-color: #66afe9; 421 | outline: 0; 422 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); 423 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); 424 | } 425 | 426 | .form-control:-moz-placeholder { 427 | color: #999999; 428 | } 429 | 430 | .form-control::-moz-placeholder { 431 | color: #999999; 432 | opacity: 1; 433 | } 434 | 435 | .form-control:-ms-input-placeholder { 436 | color: #999999; 437 | } 438 | 439 | .form-control::-webkit-input-placeholder { 440 | color: #999999; 441 | } 442 | 443 | .form-control[disabled], 444 | .form-control[readonly], 445 | fieldset[disabled] .form-control { 446 | cursor: not-allowed; 447 | background-color: #eeeeee; 448 | } 449 | 450 | textarea.form-control { 451 | height: auto; 452 | } 453 | 454 | 455 | .btn { 456 | display: inline-block; 457 | padding: 6px 12px; 458 | margin-bottom: 0; 459 | font-size: 14px; 460 | font-weight: normal; 461 | line-height: 1.428571429; 462 | text-align: center; 463 | white-space: nowrap; 464 | vertical-align: middle; 465 | cursor: pointer; 466 | background-image: none; 467 | border: 1px solid transparent; 468 | border-radius: 4px; 469 | -webkit-user-select: none; 470 | -moz-user-select: none; 471 | -ms-user-select: none; 472 | -o-user-select: none; 473 | user-select: none; 474 | } 475 | 476 | 477 | 478 | .btn-primary { 479 | color: #ffffff; 480 | background-color: #428bca; 481 | border-color: #357ebd; 482 | } 483 | 484 | .btn-primary:hover, 485 | .btn-primary:focus, 486 | .btn-primary:active, 487 | .btn-primary.active, 488 | .open .dropdown-toggle.btn-primary { 489 | color: #ffffff; 490 | background-color: #3276b1; 491 | border-color: #285e8e; 492 | } 493 | 494 | .btn-primary:active, 495 | .btn-primary.active, 496 | .open .dropdown-toggle.btn-primary { 497 | background-image: none; 498 | } 499 | 500 | .btn-primary.disabled, 501 | .btn-primary[disabled], 502 | fieldset[disabled] .btn-primary, 503 | .btn-primary.disabled:hover, 504 | .btn-primary[disabled]:hover, 505 | fieldset[disabled] .btn-primary:hover, 506 | .btn-primary.disabled:focus, 507 | .btn-primary[disabled]:focus, 508 | fieldset[disabled] .btn-primary:focus, 509 | .btn-primary.disabled:active, 510 | .btn-primary[disabled]:active, 511 | fieldset[disabled] .btn-primary:active, 512 | .btn-primary.disabled.active, 513 | .btn-primary[disabled].active, 514 | fieldset[disabled] .btn-primary.active { 515 | background-color: #428bca; 516 | border-color: #357ebd; 517 | } 518 | 519 | .btn-primary .badge { 520 | color: #428bca; 521 | background-color: #fff; 522 | } 523 | 524 | 525 | .btn-success { 526 | color: #ffffff; 527 | background-color: #5cb85c; 528 | border-color: #4cae4c; 529 | } 530 | 531 | .btn-success:hover, 532 | .btn-success:focus, 533 | .btn-success:active, 534 | .btn-success.active, 535 | .open .dropdown-toggle.btn-success { 536 | color: #ffffff; 537 | background-color: #47a447; 538 | border-color: #398439; 539 | } 540 | 541 | .btn-success:active, 542 | .btn-success.active, 543 | .open .dropdown-toggle.btn-success { 544 | background-image: none; 545 | } 546 | 547 | .btn-success.disabled, 548 | .btn-success[disabled], 549 | fieldset[disabled] .btn-success, 550 | .btn-success.disabled:hover, 551 | .btn-success[disabled]:hover, 552 | fieldset[disabled] .btn-success:hover, 553 | .btn-success.disabled:focus, 554 | .btn-success[disabled]:focus, 555 | fieldset[disabled] .btn-success:focus, 556 | .btn-success.disabled:active, 557 | .btn-success[disabled]:active, 558 | fieldset[disabled] .btn-success:active, 559 | .btn-success.disabled.active, 560 | .btn-success[disabled].active, 561 | fieldset[disabled] .btn-success.active { 562 | background-color: #5cb85c; 563 | border-color: #4cae4c; 564 | } 565 | 566 | .btn-success .badge { 567 | color: #5cb85c; 568 | background-color: #fff; 569 | } 570 | 571 | 572 | 573 | 574 | .fade { 575 | opacity: 0; 576 | -webkit-transition: opacity 0.15s linear; 577 | transition: opacity 0.15s linear; 578 | } 579 | 580 | .fade.in { 581 | opacity: 1; 582 | } 583 | 584 | .collapse { 585 | display: none; 586 | } 587 | 588 | .collapse.in { 589 | display: block; 590 | } 591 | 592 | .collapsing { 593 | position: relative; 594 | height: 0; 595 | overflow: hidden; 596 | -webkit-transition: height 0.35s ease; 597 | transition: height 0.35s ease; 598 | } 599 | 600 | 601 | /*�����*/ 602 | 603 | .input-group { 604 | position: relative; 605 | display: table; 606 | border-collapse: separate; 607 | } 608 | 609 | .input-group[class*="col-"] { 610 | float: none; 611 | padding-right: 0; 612 | padding-left: 0; 613 | } 614 | 615 | .input-group .form-control { 616 | width: 100%; 617 | margin-bottom: 0; 618 | } 619 | 620 | .input-group-btn, 621 | .input-group .form-control { 622 | display: table-cell; 623 | } 624 | 625 | .input-group-btn:not(:first-child):not(:last-child), 626 | .input-group .form-control:not(:first-child):not(:last-child) { 627 | border-radius: 0; 628 | } 629 | 630 | .input-group-addon, 631 | .input-group-btn { 632 | width: 1%; 633 | white-space: nowrap; 634 | vertical-align: middle; 635 | } 636 | 637 | 638 | /*�����˵�*/ 639 | .nav { 640 | padding-left: 0; 641 | margin-bottom: 0; 642 | list-style: none; 643 | } 644 | .nav > li { 645 | position: relative; 646 | display: block; 647 | } 648 | .nav > li > a { 649 | position: relative; 650 | display: block; 651 | padding: 10px 15px; 652 | } 653 | 654 | .nav > li > a:hover, 655 | .nav > li > a:focus { 656 | text-decoration: none; 657 | background-color: #eeeeee; 658 | } 659 | 660 | .nav > li > a > img { 661 | max-width: none; 662 | } 663 | 664 | .navbar { 665 | position: relative; 666 | min-height: 50px; 667 | margin-bottom: 20px; 668 | border: 1px solid transparent; 669 | } 670 | 671 | @media (min-width: 768px) { 672 | .navbar { 673 | border-radius: 4px; 674 | } 675 | } 676 | 677 | .navbar-header:before, 678 | .navbar-header:after { 679 | display: table; 680 | content: " "; 681 | } 682 | 683 | .navbar-header:after { 684 | clear: both; 685 | } 686 | 687 | .navbar-header:before, 688 | .navbar-header:after { 689 | display: table; 690 | content: " "; 691 | } 692 | 693 | .navbar-header:after { 694 | clear: both; 695 | } 696 | 697 | @media (min-width: 768px) { 698 | .navbar-header { 699 | float: left; 700 | } 701 | } 702 | 703 | .navbar-collapse { 704 | max-height: 340px; 705 | padding-right: 15px; 706 | padding-left: 15px; 707 | overflow-x: visible; 708 | border-top: 1px solid transparent; 709 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); 710 | -webkit-overflow-scrolling: touch; 711 | } 712 | 713 | .navbar-collapse:before, 714 | .navbar-collapse:after { 715 | display: table; 716 | content: " "; 717 | } 718 | 719 | .navbar-collapse:after { 720 | clear: both; 721 | } 722 | 723 | .navbar-collapse:before, 724 | .navbar-collapse:after { 725 | display: table; 726 | content: " "; 727 | } 728 | 729 | .navbar-collapse:after { 730 | clear: both; 731 | } 732 | 733 | .navbar-collapse.in { 734 | overflow-y: auto; 735 | } 736 | 737 | @media (min-width: 768px) { 738 | .navbar-collapse { 739 | width: auto; 740 | border-top: 0; 741 | box-shadow: none; 742 | } 743 | .navbar-collapse.collapse { 744 | display: block !important; 745 | height: auto !important; 746 | padding-bottom: 0; 747 | overflow: visible !important; 748 | } 749 | .navbar-collapse.in { 750 | overflow-y: visible; 751 | } 752 | .navbar-fixed-top .navbar-collapse, 753 | .navbar-static-top .navbar-collapse, 754 | .navbar-fixed-bottom .navbar-collapse { 755 | padding-right: 0; 756 | padding-left: 0; 757 | } 758 | } 759 | 760 | .container > .navbar-header, 761 | .container > .navbar-collapse { 762 | margin-right: -15px; 763 | margin-left: -15px; 764 | } 765 | 766 | @media (min-width: 768px) { 767 | .container > .navbar-header, 768 | .container > .navbar-collapse { 769 | margin-right: 0; 770 | margin-left: 0; 771 | } 772 | } 773 | 774 | .navbar-static-top { 775 | z-index: 1000; 776 | border-width: 0 0 1px; 777 | } 778 | 779 | @media (min-width: 768px) { 780 | .navbar-static-top { 781 | border-radius: 0; 782 | } 783 | } 784 | 785 | .navbar-fixed-top, 786 | .navbar-fixed-bottom { 787 | position: fixed; 788 | right: 0; 789 | left: 0; 790 | z-index: 1030; 791 | } 792 | 793 | @media (min-width: 768px) { 794 | .navbar-fixed-top, 795 | .navbar-fixed-bottom { 796 | border-radius: 0; 797 | } 798 | } 799 | 800 | .navbar-fixed-top { 801 | top: 0; 802 | border-width: 0 0 1px; 803 | } 804 | 805 | .navbar-fixed-bottom { 806 | bottom: 0; 807 | margin-bottom: 0; 808 | border-width: 1px 0 0; 809 | } 810 | 811 | .navbar-brand { 812 | float: left; 813 | padding: 15px 15px; 814 | font-size: 18px; 815 | line-height: 20px; 816 | } 817 | 818 | .navbar-brand { 819 | float: left; 820 | padding: 15px 15px; 821 | font-size: 18px; 822 | line-height: 20px; 823 | } 824 | 825 | .navbar-brand:hover, 826 | .navbar-brand:focus { 827 | text-decoration: none; 828 | } 829 | 830 | @media (min-width: 768px) { 831 | .navbar > .container .navbar-brand { 832 | margin-left: -15px; 833 | } 834 | } 835 | 836 | .navbar-inverse { 837 | background-color: #222222; 838 | border-color: #080808; 839 | } 840 | 841 | .navbar-inverse .navbar-brand { 842 | color: #999999; 843 | } 844 | 845 | .navbar-inverse .navbar-brand:hover, 846 | .navbar-inverse .navbar-brand:focus { 847 | color: #ffffff; 848 | background-color: transparent; 849 | } 850 | 851 | @media (min-width: 768px) { 852 | .navbar-nav { 853 | float: left; 854 | margin: 0; 855 | } 856 | .navbar-nav > li { 857 | float: left; 858 | } 859 | .navbar-nav > li > a { 860 | padding-top: 15px; 861 | padding-bottom: 15px; 862 | } 863 | .navbar-nav.navbar-right:last-child { 864 | margin-right: -15px; 865 | } 866 | } 867 | 868 | .navbar-inverse { 869 | border-radius: 0; 870 | background: rgba(0, 0, 0, .9); 871 | border-bottom: 1px solid rgba(255, 255, 255, 0.15); 872 | min-height: 100px; 873 | padding-top: 25px; 874 | margin-bottom: 0; 875 | } 876 | 877 | .navbar-inverse .navbar-brand { 878 | font-size: 24px; 879 | color: white; 880 | padding: 0 0 0 15px; 881 | margin: 12px 0 0 0; 882 | } 883 | 884 | .navbar-inverse .navbar-brand img { 885 | margin-top: -8px; 886 | } 887 | 888 | .navbar-inverse .navbar-nav > li > a, 889 | .navbar-inverse .navbar-nav > .open ul > a { 890 | color: rgba(255, 255, 255, .4); 891 | } 892 | 893 | .navbar-inverse .navbar-nav > .active > a, 894 | .navbar-inverse .navbar-nav > .active > a:hover, 895 | .navbar-inverse .navbar-nav > .active > a:focus { 896 | color: #fff; 897 | background: none; 898 | box-shadow: none; 899 | } 900 | 901 | .navbar-inverse .navbar-nav > .open > a { 902 | background: none; 903 | color: white; 904 | box-shadow: none; 905 | } 906 | 907 | .navbar-inverse .navbar-nav > li > a:hover, 908 | .navbar-inverse .navbar-nav > li > a:focus, 909 | .navbar-inverse .navbar-nav > .open > a:hover, 910 | .navbar-inverse .navbar-nav > .open > a:focus { 911 | background: none; 912 | color: white; 913 | } 914 | 915 | .jumbotron { 916 | padding: 50px; 917 | margin-bottom: 30px; 918 | font-size: 21px; 919 | font-weight: 200; 920 | line-height: 2.1428571435; 921 | color: inherit; 922 | background-color: #FFFFFF; 923 | } 924 | 925 | .jumbotron h1, 926 | .jumbotron .h1 { 927 | line-height: 1; 928 | color: inherit; 929 | } 930 | 931 | .jumbotron p { 932 | line-height: 1.4; 933 | } 934 | 935 | .container .jumbotron { 936 | border-radius: 6px; 937 | } 938 | 939 | .jumbotron .container { 940 | max-width: 100%; 941 | } 942 | 943 | /*������*/ 944 | 945 | .progress { 946 | height: 20px; 947 | margin-bottom: 20px; 948 | overflow: hidden; 949 | background-color: #f5f5f5; 950 | border-radius: 4px; 951 | -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); 952 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); 953 | } 954 | 955 | .progress-bar { 956 | float: left; 957 | width: 0; 958 | height: 100%; 959 | font-size: 12px; 960 | line-height: 20px; 961 | color: #ffffff; 962 | text-align: center; 963 | background-color: #428bca; 964 | -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); 965 | box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); 966 | -webkit-transition: width 0.6s ease; 967 | transition: width 0.6s ease; 968 | } 969 | 970 | .progress-striped .progress-bar { 971 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); 972 | background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); 973 | background-size: 40px 40px; 974 | } 975 | 976 | .progress.active .progress-bar { 977 | -webkit-animation: progress-bar-stripes 2s linear infinite; 978 | animation: progress-bar-stripes 2s linear infinite; 979 | } 980 | 981 | .progress-bar-success { 982 | background-color: #5cb85c; 983 | } 984 | 985 | .progress-striped .progress-bar-success { 986 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); 987 | background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); 988 | } 989 | 990 | .progress-bar-info { 991 | background-color: #5bc0de; 992 | } 993 | 994 | .progress-striped .progress-bar-info { 995 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); 996 | background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); 997 | } 998 | 999 | .progress-bar-warning { 1000 | background-color: #f0ad4e; 1001 | } 1002 | 1003 | .progress-striped .progress-bar-warning { 1004 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); 1005 | background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); 1006 | } 1007 | 1008 | .progress-bar-danger { 1009 | background-color: #d9534f; 1010 | } 1011 | 1012 | .progress-striped .progress-bar-danger { 1013 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); 1014 | background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); 1015 | } 1016 | 1017 | .pull-right { 1018 | float: right !important; 1019 | } 1020 | .pull-left { 1021 | float: left !important; 1022 | } 1023 | .hide { 1024 | display: none !important; 1025 | } 1026 | .show { 1027 | display: block !important; 1028 | } 1029 | 1030 | div.progress { 1031 | display: block; 1032 | height: 22px; 1033 | padding: 0; 1034 | min-width: 200px; 1035 | margin:4px 0; 1036 | background-color: #DEDEDE; 1037 | background: -moz-linear-gradient(top, #ccc, #e9e9e9); 1038 | background: -webkit-gradient(linear, left top, bottom, #ccc, #e9e9e9); 1039 | } 1040 | div.progress, div.progress span { 1041 | -moz-border-radius: 4px; 1042 | -webkit-border-radius: 4px; 1043 | border-radius: 4px; 1044 | } 1045 | div.progress span { 1046 | display: block; 1047 | /*margin-top: -10px;*/ 1048 | padding: 0; 1049 | text-align:center; 1050 | /*width:0; 1051 | -moz-box-shadow:1px 0 1px rgba(0, 0, 0, 0.2); 1052 | -webkit-box-shadow:1px 0 1px rgba(0, 0, 0, 0.2);*/ 1053 | box-shadow:1px 0 1px rgba(0, 0, 0, 0.2); 1054 | } 1055 | div.progress span b{ 1056 | color:#8e2121; 1057 | line-height:22px; 1058 | padding-left:0; 1059 | font-size:18px; 1060 | text-shadow:0 1px 1px rgba(0, 0, 0, 0.5); 1061 | } 1062 | /*-Xia La*/ 1063 | .navbar-toggle { 1064 | position: relative; 1065 | float: right; 1066 | padding: 9px 10px; 1067 | margin-top: 8px; 1068 | margin-right: 15px; 1069 | margin-bottom: 8px; 1070 | background-color: transparent; 1071 | background-image: none; 1072 | border: 1px solid transparent; 1073 | border-radius: 4px; 1074 | } 1075 | 1076 | .navbar-toggle .icon-bar { 1077 | display: block; 1078 | width: 22px; 1079 | height: 2px; 1080 | border-radius: 1px; 1081 | } 1082 | 1083 | .navbar-toggle .icon-bar + .icon-bar { 1084 | margin-top: 4px; 1085 | } 1086 | 1087 | @media (min-width: 768px) { 1088 | .navbar-toggle { 1089 | display: none; 1090 | } 1091 | } 1092 | .navbar-default .navbar-toggle .icon-bar { 1093 | background-color: #cccccc; 1094 | } 1095 | .navbar-inverse .navbar-toggle .icon-bar { 1096 | background-color: #ffffff; 1097 | } -------------------------------------------------------------------------------- /css/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asphxg/findpass/5a6708bda5be5cac1837e10f7a072ed3d9e2f03d/css/favicon.ico -------------------------------------------------------------------------------- /css/isdc-theme.css: -------------------------------------------------------------------------------- 1 | 2 | /* Header */ 3 | 4 | #head { 5 | background: #181015 no-repeat; 6 | background-size: cover; 7 | min-height: 520px; 8 | text-align: center; 9 | padding-top: 240px; 10 | color: white; 11 | font-family: "Open sans", Helvetica, Arial; 12 | font-weight: 300; 13 | } 14 | 15 | #head.secondary { 16 | height: 100px; 17 | min-height: 100px; 18 | padding-top: 0px; 19 | } 20 | 21 | #head .lead { 22 | font-family: "Open sans", Helvetica, Arial; 23 | font-size: 44px; 24 | margin-bottom: 6px; 25 | color: white; 26 | line-height: 1.15em; 27 | } 28 | 29 | #head .tagline { 30 | color: rgba(255, 255, 255, 0.75); 31 | margin-bottom: 25px; 32 | } 33 | 34 | #head .tagline a { 35 | color: #fff; 36 | } 37 | 38 | #head .btn { 39 | margin-bottom: 10px; 40 | } 41 | 42 | #head .btn-default { 43 | text-shadow: none; 44 | background: transparent; 45 | color: rgba(255, 255, 255, .5); 46 | -webkit-box-shadow: inset 0px 0px 0px 3px rgba(255, 255, 255, .5); 47 | -moz-box-shadow: inset 0px 0px 0px 3px rgba(255, 255, 255, .5); 48 | box-shadow: inset 0px 0px 0px 3px rgba(255, 255, 255, .5); 49 | background: transparent; 50 | } 51 | 52 | #head .btn-default:hover, 53 | #head .btn-default:focus { 54 | color: rgba(255, 255, 255, .8); 55 | -webkit-box-shadow: inset 0px 0px 0px 3px rgba(255, 255, 255, .8); 56 | -moz-box-shadow: inset 0px 0px 0px 3px rgba(255, 255, 255, .8); 57 | box-shadow: inset 0px 0px 0px 3px rgba(255, 255, 255, .8); 58 | background: transparent; 59 | } 60 | 61 | #head .btn-default:active, 62 | #head .btn-default.active { 63 | color: #fff; 64 | -webkit-box-shadow: inset 0px 0px 0px 3px #fff; 65 | -moz-box-shadow: inset 0px 0px 0px 3px #fff; 66 | box-shadow: inset 0px 0px 0px 3px #fff; 67 | background: transparent; 68 | } 69 | 70 | @media (max-width: 767px) { 71 | #head { 72 | min-height: 420px; 73 | padding-top: 160px; 74 | } 75 | #head .lead { 76 | font-size: 34px; 77 | } 78 | } 79 | /* Autohide navbar */ 80 | 81 | .slideUp { 82 | top: -100px; 83 | } 84 | 85 | .headroom { 86 | -webkit-transition: all 0.4s ease-out; 87 | -moz-transition: all 0.4s ease-out; 88 | -o-transition: all 0.4s ease-out; 89 | transition: all 0.4s ease-out; 90 | } 91 | /* Highlights (in jumbotron in most cases) */ 92 | 93 | .highlight { 94 | margin-top: 40px; 95 | } 96 | 97 | .h-caption { 98 | text-align: center; 99 | } 100 | 101 | .h-caption i { 102 | display: block; 103 | font-size: 54px; 104 | color: #382526; 105 | margin-bottom: 36px; 106 | } 107 | 108 | .h-caption h4 { 109 | color: #382526; 110 | font-size: 16px; 111 | font-weight: bold; 112 | margin-bottom: 20px; 113 | } 114 | 115 | .h-body {} 116 | /* Typography */ 117 | 118 | h1, 119 | h2, 120 | h3, 121 | h4, 122 | h5, 123 | h6 { 124 | font-family: "Open sans", Helvetica, Arial; 125 | } 126 | 127 | h1, 128 | .h1, 129 | h2, 130 | .h2, 131 | h3, 132 | .h3 { 133 | margin-top: 30px; 134 | } 135 | 136 | blockquote { 137 | font-style: italic; 138 | font-family: Georgia; 139 | color: #999; 140 | margin: 30px 0 30px; 141 | } 142 | 143 | label { 144 | color: #777; 145 | } 146 | 147 | .thin { 148 | font-weight: 300; 149 | } 150 | 151 | .page-title { 152 | margin-top: 20px; 153 | font-weight: 300; 154 | } 155 | 156 | .text-muted { 157 | color: #888; 158 | } 159 | 160 | .breadcrumb { 161 | background: none; 162 | padding: 0; 163 | margin: 30px 0 0px 0; 164 | } 165 | 166 | ul.list-spaces li { 167 | margin-bottom: 10px; 168 | } 169 | /* Helpers */ 170 | 171 | .container-full { 172 | margin: 0 auto; 173 | width: 100%; 174 | } 175 | 176 | .top-space { 177 | margin-top: 60px; 178 | } 179 | 180 | .top-margin { 181 | margin-top: 20px; 182 | } 183 | 184 | img { 185 | max-width: 100%; 186 | } 187 | 188 | img.pull-right { 189 | margin-left: 10px; 190 | } 191 | 192 | img.pull-left { 193 | margin-right: 10px; 194 | } 195 | 196 | #map { 197 | width: 100%; 198 | height: 280px; 199 | } 200 | 201 | #social { 202 | margin-top: 50px; 203 | margin-bottom: 50px; 204 | } 205 | 206 | #social .wrapper { 207 | width: 340px; 208 | margin: 0 auto; 209 | } 210 | /* Main content block */ 211 | 212 | .maincontent {} 213 | /* Footer */ 214 | 215 | .footer1 { 216 | background: #232323; 217 | padding: 30px 0 0 0; 218 | font-size: 12px; 219 | color: #999; 220 | } 221 | 222 | .footer1 a { 223 | color: #ccc; 224 | } 225 | 226 | .footer1 a:hover { 227 | color: #fff; 228 | } 229 | 230 | .footer1 .widget { 231 | margin-bottom: 30px; 232 | } 233 | 234 | .footer1 .widget-title { 235 | font-size: 17px; 236 | font-weight: bold; 237 | color: #ccc; 238 | margin: 0 0 20px; 239 | } 240 | 241 | .footer1 .entry-meta { 242 | border-top: 1px solid #ccc; 243 | border-bottom: 1px solid #ccc; 244 | margin: 0 0 35px 0; 245 | padding: 2px 0; 246 | color: #888888; 247 | font-size: 12px; 248 | font-size: 0.75rem; 249 | } 250 | 251 | .footer1 .entry-meta a { 252 | color: #333333; 253 | } 254 | 255 | .footer1 .entry-meta .meta-in { 256 | border-top: 1px solid #ccc; 257 | border-bottom: 1px solid #ccc; 258 | padding: 10px 0; 259 | } 260 | 261 | .follow-me-icons { 262 | font-size: 30px; 263 | } 264 | 265 | .follow-me-icons i { 266 | float: left; 267 | margin: 0 10px 0 0; 268 | } 269 | 270 | .footer2 { 271 | background: rgba(0, 0, 0, .7); 272 | padding: 15px 0; 273 | color: #fff; 274 | font-size: 15px; 275 | } 276 | 277 | .footer2 a { 278 | color: #aaa; 279 | } 280 | 281 | .footer2 a:hover { 282 | color: #fff; 283 | } 284 | 285 | .footer2 p { 286 | margin: 0; 287 | } 288 | 289 | .widget-simplenav { 290 | margin-left: -5px; 291 | } 292 | 293 | .widget-simplenav a { 294 | margin: 0 5px; 295 | } 296 | /* Markdown */ 297 | 298 | .markdown body { 299 | font-family: Helvetica, arial, sans-serif; 300 | font-size: 14px; 301 | line-height: 1.6; 302 | padding-top: 10px; 303 | padding-bottom: 10px; 304 | background-color: white; 305 | padding: 30px; 306 | } 307 | 308 | .markdown body > *:first-child { 309 | margin-top: 0 !important; 310 | } 311 | 312 | .markdown body > *:last-child { 313 | margin-bottom: 0 !important; 314 | } 315 | 316 | .markdown a { 317 | color: #4183C4; 318 | } 319 | 320 | .markdown a.absent { 321 | color: #cc0000; 322 | } 323 | 324 | .markdown a.anchor { 325 | display: block; 326 | padding-left: 30px; 327 | margin-left: -30px; 328 | cursor: pointer; 329 | position: absolute; 330 | top: 0; 331 | left: 0; 332 | bottom: 0; 333 | } 334 | 335 | .markdown h1, 336 | .markdown h2, 337 | .markdown h3, 338 | .markdown h4, 339 | .markdown h5, 340 | .markdown h6 { 341 | margin: 20px 0 10px; 342 | padding: 0; 343 | font-weight: bold; 344 | -webkit-font-smoothing: antialiased; 345 | cursor: text; 346 | position: relative; 347 | } 348 | 349 | .markdown h1:hover a.anchor, 350 | .markdown h2:hover a.anchor, 351 | .markdown h3:hover a.anchor, 352 | .markdown h4:hover a.anchor, 353 | .markdown h5:hover a.anchor, 354 | .markdown h6:hover a.anchor { 355 | background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA09pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoMTMuMCAyMDEyMDMwNS5tLjQxNSAyMDEyLzAzLzA1OjIxOjAwOjAwKSAgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUM2NjlDQjI4ODBGMTFFMTg1ODlEODNERDJBRjUwQTQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUM2NjlDQjM4ODBGMTFFMTg1ODlEODNERDJBRjUwQTQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5QzY2OUNCMDg4MEYxMUUxODU4OUQ4M0REMkFGNTBBNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5QzY2OUNCMTg4MEYxMUUxODU4OUQ4M0REMkFGNTBBNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsQhXeAAAABfSURBVHjaYvz//z8DJYCRUgMYQAbAMBQIAvEqkBQWXI6sHqwHiwG70TTBxGaiWwjCTGgOUgJiF1J8wMRAIUA34B4Q76HUBelAfJYSA0CuMIEaRP8wGIkGMA54bgQIMACAmkXJi0hKJQAAAABJRU5ErkJggg==) no-repeat 10px center; 356 | text-decoration: none; 357 | } 358 | 359 | .markdown h1 tt, 360 | .markdown h1 code { 361 | font-size: inherit; 362 | } 363 | 364 | .markdown h2 tt, 365 | .markdown h2 code { 366 | font-size: inherit; 367 | } 368 | 369 | .markdown h3 tt, 370 | .markdown h3 code { 371 | font-size: inherit; 372 | } 373 | 374 | .markdown h4 tt, 375 | .markdown h4 code { 376 | font-size: inherit; 377 | } 378 | 379 | .markdown h5 tt, 380 | .markdown h5 code { 381 | font-size: inherit; 382 | } 383 | 384 | .markdown h6 tt, 385 | .markdown h6 code { 386 | font-size: inherit; 387 | } 388 | 389 | .markdown h1 { 390 | font-size: 28px; 391 | color: black; 392 | } 393 | 394 | .markdown h2 { 395 | font-size: 24px; 396 | color: black; 397 | } 398 | 399 | .markdown h3 { 400 | font-size: 18px; 401 | } 402 | 403 | .markdown h4 { 404 | font-size: 16px; 405 | } 406 | 407 | .markdown h5 { 408 | font-size: 14px; 409 | } 410 | 411 | .markdown h6 { 412 | color: #777777; 413 | font-size: 14px; 414 | } 415 | 416 | .markdown p, 417 | .markdown blockquote, 418 | .markdown ul, 419 | .markdown ol, 420 | .markdown dl, 421 | .markdown li, 422 | .markdown table, 423 | .markdown pre { 424 | margin: 15px 0; 425 | } 426 | 427 | .markdown hr { 428 | background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OENDRjNBN0E2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OENDRjNBN0I2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4Q0NGM0E3ODY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4Q0NGM0E3OTY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqqezsUAAAAfSURBVHjaYmRABcYwBiM2QSA4y4hNEKYDQxAEAAIMAHNGAzhkPOlYAAAAAElFTkSuQmCC) repeat-x 0 0; 429 | border: 0 none; 430 | color: #cccccc; 431 | height: 4px; 432 | padding: 0; 433 | } 434 | 435 | .markdown body > h2:first-child { 436 | margin-top: 0; 437 | padding-top: 0; 438 | } 439 | 440 | .markdown body > h1:first-child { 441 | margin-top: 0; 442 | padding-top: 0; 443 | } 444 | 445 | .markdown body > h1:first-child + h2 { 446 | margin-top: 0; 447 | padding-top: 0; 448 | } 449 | 450 | .markdown body > h3:first-child, 451 | .markdown body > h4:first-child, 452 | .markdown body > h5:first-child, 453 | .markdown body > h6:first-child { 454 | margin-top: 0; 455 | padding-top: 0; 456 | } 457 | 458 | .markdown a:first-child h1, 459 | .markdown a:first-child h2, 460 | .markdown a:first-child h3, 461 | .markdown a:first-child h4, 462 | .markdown a:first-child h5, 463 | .markdown a:first-child h6 { 464 | margin-top: 0; 465 | padding-top: 0; 466 | } 467 | 468 | .markdown h1 p, 469 | .markdown h2 p, 470 | .markdown h3 p, 471 | .markdown h4 p, 472 | .markdown h5 p, 473 | .markdown h6 p { 474 | margin-top: 0; 475 | } 476 | 477 | .markdown li p.first { 478 | display: inline-block; 479 | } 480 | 481 | .markdown li { 482 | margin: 0; 483 | } 484 | 485 | .markdown ul, 486 | .markdown ol { 487 | padding-left: 30px; 488 | } 489 | 490 | .markdown ul:first-child, 491 | .markdown ol:first-child { 492 | margin-top: 0; 493 | } 494 | 495 | .markdown dl { 496 | padding: 0; 497 | } 498 | 499 | .markdown dl dt { 500 | font-size: 14px; 501 | font-weight: bold; 502 | font-style: italic; 503 | padding: 0; 504 | margin: 15px 0 5px; 505 | } 506 | 507 | .markdown dl dt:first-child { 508 | padding: 0; 509 | } 510 | 511 | .markdown dl dt >:first-child { 512 | margin-top: 0; 513 | } 514 | 515 | .markdown dl dt >:last-child { 516 | margin-bottom: 0; 517 | } 518 | 519 | .markdown dl dd { 520 | margin: 0 0 15px; 521 | padding: 0 15px; 522 | } 523 | 524 | .markdown dl dd >:first-child { 525 | margin-top: 0; 526 | } 527 | 528 | .markdown dl dd >:last-child { 529 | margin-bottom: 0; 530 | } 531 | 532 | .markdown blockquote { 533 | border-left: 4px solid #dddddd; 534 | padding: 0 15px; 535 | color: #777777; 536 | } 537 | 538 | .markdown blockquote >:first-child { 539 | margin-top: 0; 540 | } 541 | 542 | .markdown blockquote >:last-child { 543 | margin-bottom: 0; 544 | } 545 | 546 | .markdown table { 547 | padding: 0; 548 | border-collapse: collapse; 549 | } 550 | 551 | .markdown table tr { 552 | border-top: 1px solid #cccccc; 553 | background-color: white; 554 | margin: 0; 555 | padding: 0; 556 | } 557 | 558 | .markdown table tr:nth-child(2n) { 559 | background-color: #f8f8f8; 560 | } 561 | 562 | .markdown table tr th { 563 | font-weight: bold; 564 | border: 1px solid #cccccc; 565 | margin: 0; 566 | padding: 6px 13px; 567 | } 568 | 569 | .markdown table tr td { 570 | border: 1px solid #cccccc; 571 | margin: 0; 572 | padding: 6px 13px; 573 | } 574 | 575 | .markdown table tr th:first-child, 576 | .markdown table tr td:first-child { 577 | margin-top: 0; 578 | } 579 | 580 | .markdown table tr th:last-child, 581 | .markdown table tr td:last-child { 582 | margin-bottom: 0; 583 | } 584 | 585 | .markdown img { 586 | max-width: 100%; 587 | } 588 | 589 | .markdown span.frame { 590 | display: block; 591 | overflow: hidden; 592 | } 593 | 594 | .markdown span.frame > span { 595 | border: 1px solid #dddddd; 596 | display: block; 597 | float: left; 598 | overflow: hidden; 599 | margin: 13px 0 0; 600 | padding: 7px; 601 | width: auto; 602 | } 603 | 604 | .markdown span.frame span img { 605 | display: block; 606 | float: left; 607 | } 608 | 609 | .markdown span.frame span span { 610 | clear: both; 611 | color: #333333; 612 | display: block; 613 | padding: 5px 0 0; 614 | } 615 | 616 | .markdown span.align-center { 617 | display: block; 618 | overflow: hidden; 619 | clear: both; 620 | } 621 | 622 | .markdown span.align-center > span { 623 | display: block; 624 | overflow: hidden; 625 | margin: 13px auto 0; 626 | text-align: center; 627 | } 628 | 629 | .markdown span.align-center span img { 630 | margin: 0 auto; 631 | text-align: center; 632 | } 633 | 634 | .markdown span.align-right { 635 | display: block; 636 | overflow: hidden; 637 | clear: both; 638 | } 639 | 640 | .markdown span.align-right > span { 641 | display: block; 642 | overflow: hidden; 643 | margin: 13px 0 0; 644 | text-align: right; 645 | } 646 | 647 | .markdown span.align-right span img { 648 | margin: 0; 649 | text-align: right; 650 | } 651 | 652 | .markdown span.float-left { 653 | display: block; 654 | margin-right: 13px; 655 | overflow: hidden; 656 | float: left; 657 | } 658 | 659 | .markdown span.float-left span { 660 | margin: 13px 0 0; 661 | } 662 | 663 | .markdown span.float-right { 664 | display: block; 665 | margin-left: 13px; 666 | overflow: hidden; 667 | float: right; 668 | } 669 | 670 | .markdown span.float-right > span { 671 | display: block; 672 | overflow: hidden; 673 | margin: 13px auto 0; 674 | text-align: right; 675 | } 676 | 677 | .markdown code, 678 | .markdown tt { 679 | margin: 0 2px; 680 | padding: 2px 4px; 681 | color: #008B45; 682 | white-space: nowrap; 683 | border: 1px solid #eaeaea; 684 | background-color: #f8f8f8; 685 | border-radius: 3px; 686 | } 687 | 688 | .markdown pre code { 689 | margin: 0; 690 | padding: 0; 691 | white-space: pre; 692 | border: none; 693 | background: transparent; 694 | } 695 | 696 | .markdown .highlight pre { 697 | background-color: #f8f8f8; 698 | border: 1px solid #cccccc; 699 | font-size: 13px; 700 | line-height: 19px; 701 | overflow: auto; 702 | padding: 6px 10px; 703 | border-radius: 3px; 704 | } 705 | 706 | .markdown pre { 707 | background-color: #f8f8f8; 708 | border: 1px solid #cccccc; 709 | font-size: 13px; 710 | line-height: 19px; 711 | overflow: auto; 712 | padding: 6px 10px; 713 | border-radius: 3px; 714 | } 715 | 716 | .markdown pre code, 717 | .markdown pre tt { 718 | background-color: transparent; 719 | border: none; 720 | } 721 | 722 | .markdown sup { 723 | font-size: 0.83em; 724 | vertical-align: super; 725 | line-height: 0; 726 | } 727 | 728 | .markdown * { 729 | -webkit-print-color-adjust: exact; 730 | } 731 | /**/ 732 | 733 | .jumbotron { 734 | color: inherit; 735 | 736 | padding-top: 70px; 737 | padding-bottom: 30px; 738 | margin-bottom: 0; 739 | } 740 | 741 | .container .jumbotron { 742 | -webkit-border-radius: 3px; 743 | -moz-border-radius: 3px; 744 | border-radius: 3px; 745 | padding-left: 40px; 746 | padding-right: 40px; 747 | } 748 | 749 | .jumbotron p { 750 | font-size: inherit; 751 | } 752 | 753 | .jumbotron h2, 754 | .jumbotron h3, 755 | .jumbotron h4, 756 | .jumbotron h5, 757 | .jumbotron h6 { 758 | line-height: 1.3em; 759 | } 760 | 761 | .img-rounded { 762 | -webkit-border-radius: 3px; 763 | -moz-border-radius: 3px; 764 | border-radius: 3px; 765 | } -------------------------------------------------------------------------------- /css/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asphxg/findpass/5a6708bda5be5cac1837e10f7a072ed3d9e2f03d/css/logo.png -------------------------------------------------------------------------------- /data.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat MySQL Data Transfer 3 | 4 | Date: 2019 5 | */ 6 | 7 | SET FOREIGN_KEY_CHECKS=0; 8 | 9 | -- ---------------------------- 10 | -- Table structure for `data` 11 | -- ---------------------------- 12 | DROP TABLE IF EXISTS `data`; 13 | CREATE TABLE `data` ( 14 | `id` int(30) unsigned NOT NULL AUTO_INCREMENT, 15 | `username` char(48) NOT NULL DEFAULT '', 16 | `password` char(64) NOT NULL DEFAULT '', 17 | `salt` char(48) DEFAULT '', 18 | `email` char(48) DEFAULT '', 19 | `order` char(20) DEFAULT '未知' COMMENT '来源', 20 | PRIMARY KEY (`id`), 21 | KEY `email` (`email`(20)) 22 | ) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=gbk; 23 | 24 | -- ---------------------------- 25 | -- Records of data 26 | -- ---------------------------- 27 | INSERT INTO `data` VALUES ('1', 'admin', 'admin888', '', 'admin@qq.com', 'test'); 28 | INSERT INTO `data` VALUES ('2', '123456', '123456', '', '123456@qq.com', 'test'); 29 | INSERT INTO `data` VALUES ('3', '111111', '111111', '', '111111@qq.com', 'test'); 30 | INSERT INTO `data` VALUES ('4', '222222', '222222', '', '222222@qq.com', 'test'); 31 | INSERT INTO `data` VALUES ('5', '333333', '333333', '', '333333@qq.com', 'test'); 32 | INSERT INTO `data` VALUES ('6', '444444', '444444', '', '444444@qq.com', 'test'); 33 | INSERT INTO `data` VALUES ('7', '555555', '555555', '', '555555@qq.com', 'test'); 34 | INSERT INTO `data` VALUES ('8', '666666', '666666', '', '666666@qq.com', 'test'); 35 | INSERT INTO `data` VALUES ('9', '777777', '777777', '', '777777@qq.com', 'test'); 36 | INSERT INTO `data` VALUES ('10', '888888', '888888', '', '888888@qq.com', 'test'); 37 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | SetServer('127.0.0.1', 9321); 18 | $cl->SetConnectTimeout(3); 19 | $cl->SetArrayResult(true); 20 | // 设置是否全文匹配 21 | if (!empty($_GET) && !empty($_GET['f'])) { 22 | $cl->SetMatchMode(SPH_MATCH_ALL); 23 | } else { 24 | $cl->SetMatchMode(SPH_MATCH_ANY); 25 | } 26 | if (!empty($_GET) && !empty($_GET['p'])) { 27 | $p = !intval(trim($_GET['p'])) == 0 ? intval(trim($_GET['p'])) - 1 : 0; 28 | $p = $p * 20; 29 | // 我在sed.conf 设置了最大返回结果数1000。但是我在生成页码的时候最多生成20页,我想能满足大部分搜索需求了。 30 | // 以下语句表示从P参数偏移开始每次返回20条。 31 | $cl->setLimits($p, 20); 32 | } else { 33 | $cl->setLimits(0, 20); 34 | } 35 | $res = $cl->Query(".$Keywords.", "*"); 36 | @mysql_connect("127.0.0.1:3306", "root", "root123456"); //数据库账号密码 37 | mysql_select_db("sgkdata"); //数据库库名名 38 | 39 | if (is_array($res["matches"])) { 40 | foreach ($res["matches"] as $docinfo) { 41 | $ids = $ids . $docinfo[id] . ','; 42 | } 43 | $ids = rtrim($ids, ','); 44 | $sql = "select * from data where id in($ids)"; //注意修改表名 45 | mysql_query("set names utf8"); 46 | $ret = mysql_query($sql); 47 | $num = mysql_num_rows($ret); 48 | } 49 | 50 | } 51 | ?> 52 | 53 | 54 | 55 | FindPass 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 82 | 87 | 166 | 167 | 178 | 179 | -------------------------------------------------------------------------------- /sphinx/bin/1cmd.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asphxg/findpass/5a6708bda5be5cac1837e10f7a072ed3d9e2f03d/sphinx/bin/1cmd.bat -------------------------------------------------------------------------------- /sphinx/bin/1start.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asphxg/findpass/5a6708bda5be5cac1837e10f7a072ed3d9e2f03d/sphinx/bin/1start.bat -------------------------------------------------------------------------------- /sphinx/bin/1索引.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asphxg/findpass/5a6708bda5be5cac1837e10f7a072ed3d9e2f03d/sphinx/bin/1索引.bat -------------------------------------------------------------------------------- /sphinx/bin/indexer.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asphxg/findpass/5a6708bda5be5cac1837e10f7a072ed3d9e2f03d/sphinx/bin/indexer.exe -------------------------------------------------------------------------------- /sphinx/bin/indextool.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asphxg/findpass/5a6708bda5be5cac1837e10f7a072ed3d9e2f03d/sphinx/bin/indextool.exe -------------------------------------------------------------------------------- /sphinx/bin/libeay32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asphxg/findpass/5a6708bda5be5cac1837e10f7a072ed3d9e2f03d/sphinx/bin/libeay32.dll -------------------------------------------------------------------------------- /sphinx/bin/libiconv-2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asphxg/findpass/5a6708bda5be5cac1837e10f7a072ed3d9e2f03d/sphinx/bin/libiconv-2.dll -------------------------------------------------------------------------------- /sphinx/bin/libintl-8.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asphxg/findpass/5a6708bda5be5cac1837e10f7a072ed3d9e2f03d/sphinx/bin/libintl-8.dll -------------------------------------------------------------------------------- /sphinx/bin/libmariadb.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asphxg/findpass/5a6708bda5be5cac1837e10f7a072ed3d9e2f03d/sphinx/bin/libmariadb.dll -------------------------------------------------------------------------------- /sphinx/bin/libpq.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asphxg/findpass/5a6708bda5be5cac1837e10f7a072ed3d9e2f03d/sphinx/bin/libpq.dll -------------------------------------------------------------------------------- /sphinx/bin/msvcr120.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asphxg/findpass/5a6708bda5be5cac1837e10f7a072ed3d9e2f03d/sphinx/bin/msvcr120.dll -------------------------------------------------------------------------------- /sphinx/bin/searchd.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asphxg/findpass/5a6708bda5be5cac1837e10f7a072ed3d9e2f03d/sphinx/bin/searchd.exe -------------------------------------------------------------------------------- /sphinx/bin/sphinx.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Minimal Sphinx configuration sample (clean, simple, functional) 3 | # 4 | 5 | source sgk 6 | { 7 | type = mysql 8 | sql_host = 127.0.0.1 9 | sql_user = root 10 | sql_pass = root123456 11 | sql_db = sgkdata 12 | sql_port = 3306 13 | sql_query = SELECT `id`,`username`,`password`,`email`,`salt`,`order` FROM data 14 | sql_query_info = SELECT * FROM data WHERE id=$id 15 | } 16 | 17 | index sgk 18 | { 19 | ondisk_dict = 1 20 | source = sgk 21 | path = D:/sgk/sphinx/var/data/find 22 | docinfo = extern 23 | #chinese_dictionary = D:/sgk/sphinx/etc/ 24 | charset_type = zh_cn.utf-8 25 | #ngram_len = 1 26 | #ngram_chars = U+3000..U+2FA1F 27 | } 28 | 29 | indexer 30 | { 31 | mem_limit = 1024M 32 | } 33 | 34 | searchd 35 | { 36 | listen = 9321 37 | log = D:/sgk/sphinx/var/log/searchd.log 38 | query_log = D:/sgk/sphinx/var/log/query.log 39 | read_timeout = 5 40 | max_children = 30 41 | pid_file = D:/sgk/sphinx/var/log/searchd.pid 42 | max_matches = 1000 43 | seamless_rotate = 1 44 | preopen_indexes = 1 45 | unlink_old = 1 46 | workers = threads # for RT to work 47 | binlog_path = 48 | ondisk_dict_default = 1 49 | } -------------------------------------------------------------------------------- /sphinx/bin/ssleay32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asphxg/findpass/5a6708bda5be5cac1837e10f7a072ed3d9e2f03d/sphinx/bin/ssleay32.dll -------------------------------------------------------------------------------- /sphinx/bin/wordbreaker.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asphxg/findpass/5a6708bda5be5cac1837e10f7a072ed3d9e2f03d/sphinx/bin/wordbreaker.exe -------------------------------------------------------------------------------- /sphinx/etc/example.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS test.documents; 2 | CREATE TABLE test.documents 3 | ( 4 | id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, 5 | group_id INTEGER NOT NULL, 6 | group_id2 INTEGER NOT NULL, 7 | date_added DATETIME NOT NULL, 8 | title VARCHAR(255) NOT NULL, 9 | content TEXT NOT NULL 10 | ); 11 | 12 | REPLACE INTO test.documents ( id, group_id, group_id2, date_added, title, content ) VALUES 13 | ( 1, 1, 5, NOW(), 'test one', 'this is my test document number one. also checking search within phrases.' ), 14 | ( 2, 1, 6, NOW(), 'test two', 'this is my test document number two' ), 15 | ( 3, 2, 7, NOW(), 'another doc', 'this is another group' ), 16 | ( 4, 2, 8, NOW(), 'doc number four', 'this is to test groups' ); 17 | 18 | DROP TABLE IF EXISTS test.tags; 19 | CREATE TABLE test.tags 20 | ( 21 | docid INTEGER NOT NULL, 22 | tagid INTEGER NOT NULL, 23 | UNIQUE(docid,tagid) 24 | ); 25 | 26 | INSERT INTO test.tags VALUES 27 | (1,1), (1,3), (1,5), (1,7), 28 | (2,6), (2,4), (2,2), 29 | (3,15), 30 | (4,7), (4,40); 31 | -------------------------------------------------------------------------------- /sphinx/etc/sphinx-min.conf.dist: -------------------------------------------------------------------------------- 1 | # 2 | # Minimal Sphinx configuration sample (clean, simple, functional) 3 | # 4 | 5 | source src1 6 | { 7 | type = mysql 8 | 9 | sql_host = localhost 10 | sql_user = test 11 | sql_pass = 12 | sql_db = test 13 | sql_port = 3306 # optional, default is 3306 14 | 15 | sql_query = \ 16 | SELECT id, group_id, UNIX_TIMESTAMP(date_added) AS date_added, title, content \ 17 | FROM documents 18 | 19 | sql_attr_uint = group_id 20 | sql_attr_timestamp = date_added 21 | } 22 | 23 | 24 | index test1 25 | { 26 | source = src1 27 | path = /var/data/test1 28 | } 29 | 30 | 31 | index testrt 32 | { 33 | type = rt 34 | rt_mem_limit = 128M 35 | 36 | path = /var/data/testrt 37 | 38 | rt_field = title 39 | rt_field = content 40 | rt_attr_uint = gid 41 | } 42 | 43 | 44 | indexer 45 | { 46 | mem_limit = 128M 47 | } 48 | 49 | 50 | searchd 51 | { 52 | listen = 9312 53 | listen = 9306:mysql41 54 | log = /var/log/searchd.log 55 | query_log = /var/log/query.log 56 | read_timeout = 5 57 | max_children = 30 58 | pid_file = /var/log/searchd.pid 59 | seamless_rotate = 1 60 | preopen_indexes = 1 61 | unlink_old = 1 62 | workers = threads # for RT to work 63 | binlog_path = /var/data 64 | } 65 | -------------------------------------------------------------------------------- /sphinx/etc/sphinx.conf.dist: -------------------------------------------------------------------------------- 1 | # 2 | # Sphinx configuration file sample 3 | # 4 | # WARNING! While this sample file mentions all available options, 5 | # it contains (very) short helper descriptions only. Please refer to 6 | # doc/sphinx.html for details. 7 | # 8 | 9 | ############################################################################# 10 | ## data source definition 11 | ############################################################################# 12 | 13 | source src1 14 | { 15 | # data source type. mandatory, no default value 16 | # known types are mysql, pgsql, mssql, xmlpipe, xmlpipe2, odbc 17 | type = mysql 18 | 19 | ##################################################################### 20 | ## SQL settings (for 'mysql' and 'pgsql' types) 21 | ##################################################################### 22 | 23 | # some straightforward parameters for SQL source types 24 | sql_host = localhost 25 | sql_user = test 26 | sql_pass = 27 | sql_db = test 28 | sql_port = 3306 # optional, default is 3306 29 | 30 | # UNIX socket name 31 | # optional, default is empty (reuse client library defaults) 32 | # usually '/var/lib/mysql/mysql.sock' on Linux 33 | # usually '/tmp/mysql.sock' on FreeBSD 34 | # 35 | # sql_sock = /tmp/mysql.sock 36 | 37 | 38 | # MySQL specific client connection flags 39 | # optional, default is 0 40 | # 41 | # mysql_connect_flags = 32 # enable compression 42 | 43 | # MySQL specific SSL certificate settings 44 | # optional, defaults are empty 45 | # 46 | # mysql_ssl_cert = /etc/ssl/client-cert.pem 47 | # mysql_ssl_key = /etc/ssl/client-key.pem 48 | # mysql_ssl_ca = /etc/ssl/cacert.pem 49 | 50 | # MS SQL specific Windows authentication mode flag 51 | # MUST be in sync with charset_type index-level setting 52 | # optional, default is 0 53 | # 54 | # mssql_winauth = 1 # use currently logged on user credentials 55 | 56 | 57 | # ODBC specific DSN (data source name) 58 | # mandatory for odbc source type, no default value 59 | # 60 | # odbc_dsn = DBQ=C:\data;DefaultDir=C:\data;Driver={Microsoft Text Driver (*.txt; *.csv)}; 61 | # sql_query = SELECT id, data FROM documents.csv 62 | 63 | 64 | # ODBC and MS SQL specific, per-column buffer sizes 65 | # optional, default is auto-detect 66 | # 67 | # sql_column_buffers = content=12M, comments=1M 68 | 69 | 70 | # pre-query, executed before the main fetch query 71 | # multi-value, optional, default is empty list of queries 72 | # 73 | # sql_query_pre = SET NAMES utf8 74 | # sql_query_pre = SET SESSION query_cache_type=OFF 75 | 76 | 77 | # main document fetch query 78 | # mandatory, integer document ID field MUST be the first selected column 79 | sql_query = \ 80 | SELECT id, group_id, UNIX_TIMESTAMP(date_added) AS date_added, title, content \ 81 | FROM documents 82 | 83 | 84 | # joined/payload field fetch query 85 | # joined fields let you avoid (slow) JOIN and GROUP_CONCAT 86 | # payload fields let you attach custom per-keyword values (eg. for ranking) 87 | # 88 | # syntax is FIELD-NAME 'from' ( 'query' | 'payload-query' ); QUERY 89 | # joined field QUERY should return 2 columns (docid, text) 90 | # payload field QUERY should return 3 columns (docid, keyword, weight) 91 | # 92 | # REQUIRES that query results are in ascending document ID order! 93 | # multi-value, optional, default is empty list of queries 94 | # 95 | # sql_joined_field = tags from query; SELECT docid, CONCAT('tag',tagid) FROM tags ORDER BY docid ASC 96 | # sql_joined_field = wtags from payload-query; SELECT docid, tag, tagweight FROM tags ORDER BY docid ASC 97 | 98 | 99 | # file based field declaration 100 | # 101 | # content of this field is treated as a file name 102 | # and the file gets loaded and indexed in place of a field 103 | # 104 | # max file size is limited by max_file_field_buffer indexer setting 105 | # file IO errors are non-fatal and get reported as warnings 106 | # 107 | # sql_file_field = content_file_path 108 | 109 | 110 | # range query setup, query that must return min and max ID values 111 | # optional, default is empty 112 | # 113 | # sql_query will need to reference $start and $end boundaries 114 | # if using ranged query: 115 | # 116 | # sql_query = \ 117 | # SELECT doc.id, doc.id AS group, doc.title, doc.data \ 118 | # FROM documents doc \ 119 | # WHERE id>=$start AND id<=$end 120 | # 121 | # sql_query_range = SELECT MIN(id),MAX(id) FROM documents 122 | 123 | 124 | # range query step 125 | # optional, default is 1024 126 | # 127 | # sql_range_step = 1000 128 | 129 | 130 | # unsigned integer attribute declaration 131 | # multi-value (an arbitrary number of attributes is allowed), optional 132 | # optional bit size can be specified, default is 32 133 | # 134 | # sql_attr_uint = author_id 135 | # sql_attr_uint = forum_id:9 # 9 bits for forum_id 136 | sql_attr_uint = group_id 137 | 138 | # boolean attribute declaration 139 | # multi-value (an arbitrary number of attributes is allowed), optional 140 | # equivalent to sql_attr_uint with 1-bit size 141 | # 142 | # sql_attr_bool = is_deleted 143 | 144 | 145 | # bigint attribute declaration 146 | # multi-value (an arbitrary number of attributes is allowed), optional 147 | # declares a signed (unlike uint!) 64-bit attribute 148 | # 149 | # sql_attr_bigint = my_bigint_id 150 | 151 | 152 | # UNIX timestamp attribute declaration 153 | # multi-value (an arbitrary number of attributes is allowed), optional 154 | # similar to integer, but can also be used in date functions 155 | # 156 | # sql_attr_timestamp = posted_ts 157 | # sql_attr_timestamp = last_edited_ts 158 | sql_attr_timestamp = date_added 159 | 160 | 161 | # floating point attribute declaration 162 | # multi-value (an arbitrary number of attributes is allowed), optional 163 | # values are stored in single precision, 32-bit IEEE 754 format 164 | # 165 | # sql_attr_float = lat_radians 166 | # sql_attr_float = long_radians 167 | 168 | 169 | # multi-valued attribute (MVA) attribute declaration 170 | # multi-value (an arbitrary number of attributes is allowed), optional 171 | # MVA values are variable length lists of unsigned 32-bit integers 172 | # 173 | # syntax is ATTR-TYPE ATTR-NAME 'from' SOURCE-TYPE [;QUERY] [;RANGE-QUERY] 174 | # ATTR-TYPE is 'uint' or 'timestamp' 175 | # SOURCE-TYPE is 'field', 'query', or 'ranged-query' 176 | # QUERY is SQL query used to fetch all ( docid, attrvalue ) pairs 177 | # RANGE-QUERY is SQL query used to fetch min and max ID values, similar to 'sql_query_range' 178 | # 179 | # sql_attr_multi = uint tag from query; SELECT docid, tagid FROM tags 180 | # sql_attr_multi = uint tag from ranged-query; \ 181 | # SELECT docid, tagid FROM tags WHERE id>=$start AND id<=$end; \ 182 | # SELECT MIN(docid), MAX(docid) FROM tags 183 | 184 | 185 | # string attribute declaration 186 | # multi-value (an arbitrary number of these is allowed), optional 187 | # lets you store and retrieve strings 188 | # 189 | # sql_attr_string = stitle 190 | 191 | 192 | # JSON attribute declaration 193 | # multi-value (an arbitrary number of these is allowed), optional 194 | # lets you store a JSON document as an (in-memory) attribute for later use 195 | # 196 | # sql_attr_json = properties 197 | 198 | 199 | # combined field plus attribute declaration (from a single column) 200 | # stores column as an attribute, but also indexes it as a full-text field 201 | # 202 | # sql_field_string = author 203 | 204 | 205 | # post-query, executed on sql_query completion 206 | # optional, default is empty 207 | # 208 | # sql_query_post = 209 | 210 | 211 | # post-index-query, executed on successful indexing completion 212 | # optional, default is empty 213 | # $maxid expands to max document ID actually fetched from DB 214 | # 215 | # sql_query_post_index = REPLACE INTO counters ( id, val ) \ 216 | # VALUES ( 'max_indexed_id', $maxid ) 217 | 218 | 219 | # ranged query throttling, in milliseconds 220 | # optional, default is 0 which means no delay 221 | # enforces given delay before each query step 222 | sql_ranged_throttle = 0 223 | 224 | 225 | # kill-list query, fetches the document IDs for kill-list 226 | # k-list will suppress matches from preceding indexes in the same query 227 | # optional, default is empty 228 | # 229 | # sql_query_killlist = SELECT id FROM documents WHERE edited>=@last_reindex 230 | 231 | 232 | # columns to unpack on indexer side when indexing 233 | # multi-value, optional, default is empty list 234 | # 235 | # unpack_zlib = zlib_column 236 | # unpack_mysqlcompress = compressed_column 237 | # unpack_mysqlcompress = compressed_column_2 238 | 239 | 240 | # maximum unpacked length allowed in MySQL COMPRESS() unpacker 241 | # optional, default is 16M 242 | # 243 | # unpack_mysqlcompress_maxsize = 16M 244 | 245 | 246 | # hook command to run when SQL connection succeeds 247 | # optional, default value is empty (do nothing) 248 | # 249 | # hook_connect = bash sql_connect.sh 250 | 251 | 252 | # hook command to run after (any) SQL range query 253 | # it may print out "minid maxid" (w/o quotes) to override the range 254 | # optional, default value is empty (do nothing) 255 | # 256 | # hook_query_range = bash sql_query_range.sh 257 | 258 | 259 | # hook command to run on successful indexing completion 260 | # $maxid expands to max document ID actually fetched from DB 261 | # optional, default value is empty (do nothing) 262 | # 263 | # hook_post_index = bash sql_post_index.sh $maxid 264 | 265 | ##################################################################### 266 | ## xmlpipe2 settings 267 | ##################################################################### 268 | 269 | # type = xmlpipe 270 | 271 | # shell command to invoke xmlpipe stream producer 272 | # mandatory 273 | # 274 | # xmlpipe_command = cat /var/test.xml 275 | 276 | # xmlpipe2 field declaration 277 | # multi-value, optional, default is empty 278 | # 279 | # xmlpipe_field = subject 280 | # xmlpipe_field = content 281 | 282 | 283 | # xmlpipe2 attribute declaration 284 | # multi-value, optional, default is empty 285 | # all xmlpipe_attr_XXX options are fully similar to sql_attr_XXX 286 | # examples: 287 | # 288 | # xmlpipe_attr_timestamp = published 289 | # xmlpipe_attr_uint = author_id 290 | # xmlpipe_attr_bool = is_enabled 291 | # xmlpipe_attr_float = latitude 292 | # xmlpipe_attr_bigint = guid 293 | # xmlpipe_attr_multi = tags 294 | # xmlpipe_attr_multi_64 = tags64 295 | # xmlpipe_attr_string = title 296 | # xmlpipe_attr_json = extra_data 297 | # xmlpipe_field_string = content 298 | 299 | 300 | # perform UTF-8 validation, and filter out incorrect codes 301 | # avoids XML parser choking on non-UTF-8 documents 302 | # optional, default is 0 303 | # 304 | # xmlpipe_fixup_utf8 = 1 305 | } 306 | 307 | 308 | # inherited source example 309 | # 310 | # all the parameters are copied from the parent source, 311 | # and may then be overridden in this source definition 312 | source src1throttled : src1 313 | { 314 | sql_ranged_throttle = 100 315 | } 316 | 317 | ############################################################################# 318 | ## index definition 319 | ############################################################################# 320 | 321 | # local index example 322 | # 323 | # this is an index which is stored locally in the filesystem 324 | # 325 | # all indexing-time options (such as morphology and charsets) 326 | # are configured per local index 327 | index test1 328 | { 329 | # index type 330 | # optional, default is 'plain' 331 | # known values are 'plain', 'distributed', and 'rt' (see samples below) 332 | # type = plain 333 | 334 | # document source(s) to index 335 | # multi-value, mandatory 336 | # document IDs must be globally unique across all sources 337 | source = src1 338 | 339 | # index files path and file name, without extension 340 | # mandatory, path must be writable, extensions will be auto-appended 341 | path = /var/data/test1 342 | 343 | # document attribute values (docinfo) storage mode 344 | # optional, default is 'extern' 345 | # known values are 'none', 'extern' and 'inline' 346 | docinfo = extern 347 | 348 | # dictionary type, 'crc' or 'keywords' 349 | # crc is faster to index when no substring/wildcards searches are needed 350 | # crc with substrings might be faster to search but is much slower to index 351 | # (because all substrings are pre-extracted as individual keywords) 352 | # keywords is much faster to index with substrings, and index is much (3-10x) smaller 353 | # keywords supports wildcards, crc does not, and never will 354 | # optional, default is 'keywords' 355 | dict = keywords 356 | 357 | # memory locking for cached data (.spa and .spi), to prevent swapping 358 | # optional, default is 0 (do not mlock) 359 | # requires searchd to be run from root 360 | mlock = 0 361 | 362 | # a list of morphology preprocessors to apply 363 | # optional, default is empty 364 | # 365 | # builtin preprocessors are 'none', 'stem_en', 'stem_ru', 'stem_enru', 366 | # 'soundex', and 'metaphone'; additional preprocessors available from 367 | # libstemmer are 'libstemmer_XXX', where XXX is algorithm code 368 | # (see libstemmer_c/libstemmer/modules.txt) 369 | # 370 | # morphology = stem_en, stem_ru, soundex 371 | # morphology = libstemmer_german 372 | # morphology = libstemmer_sv 373 | morphology = none 374 | 375 | # minimum word length at which to enable stemming 376 | # optional, default is 1 (stem everything) 377 | # 378 | # min_stemming_len = 1 379 | 380 | 381 | # stopword files list (space separated) 382 | # optional, default is empty 383 | # contents are plain text, charset_table and stemming are both applied 384 | # 385 | # stopwords = /var/data/stopwords.txt 386 | 387 | 388 | # wordforms file, in "mapfrom > mapto" plain text format 389 | # optional, default is empty 390 | # 391 | # wordforms = /var/data/wordforms.txt 392 | 393 | 394 | # tokenizing exceptions file 395 | # optional, default is empty 396 | # 397 | # plain text, case sensitive, space insensitive in map-from part 398 | # one "Map Several Words => ToASingleOne" entry per line 399 | # 400 | # exceptions = /var/data/exceptions.txt 401 | 402 | 403 | # embedded file size limit 404 | # optional, default is 16K 405 | # 406 | # exceptions, wordforms, and stopwords files smaller than this limit 407 | # are stored in the index; otherwise, their paths and sizes are stored 408 | # 409 | # embedded_limit = 16K 410 | 411 | # minimum indexed word length 412 | # default is 1 (index everything) 413 | min_word_len = 1 414 | 415 | 416 | # ignored characters list 417 | # optional, default value is empty 418 | # 419 | # ignore_chars = U+00AD 420 | 421 | 422 | # minimum word prefix length to index 423 | # optional, default is 0 (do not index prefixes) 424 | # 425 | # min_prefix_len = 0 426 | 427 | 428 | # minimum word infix length to index 429 | # optional, default is 0 (do not index infixes) 430 | # 431 | # min_infix_len = 0 432 | 433 | 434 | # maximum substring (prefix or infix) length to index 435 | # optional, default is 0 (do not limit substring length) 436 | # 437 | # max_substring_len = 8 438 | 439 | 440 | # list of fields to limit prefix/infix indexing to 441 | # optional, default value is empty (index all fields in prefix/infix mode) 442 | # 443 | # prefix_fields = filename 444 | # infix_fields = url, domain 445 | 446 | 447 | # expand keywords with exact forms and/or stars when searching fit indexes 448 | # search-time only, does not affect indexing, can be 0 or 1 449 | # optional, default is 0 (do not expand keywords) 450 | # 451 | # expand_keywords = 1 452 | 453 | 454 | # n-gram length to index, for CJK indexing 455 | # only supports 0 and 1 for now, other lengths to be implemented 456 | # optional, default is 0 (disable n-grams) 457 | # 458 | # ngram_len = 1 459 | 460 | 461 | # n-gram characters list, for CJK indexing 462 | # optional, default is empty 463 | # 464 | # ngram_chars = U+3000..U+2FA1F 465 | 466 | 467 | # phrase boundary characters list 468 | # optional, default is empty 469 | # 470 | # phrase_boundary = ., ?, !, U+2026 # horizontal ellipsis 471 | 472 | 473 | # phrase boundary word position increment 474 | # optional, default is 0 475 | # 476 | # phrase_boundary_step = 100 477 | 478 | 479 | # blended characters list 480 | # blended chars are indexed both as separators and valid characters 481 | # for instance, AT&T will results in 3 tokens ("at", "t", and "at&t") 482 | # optional, default is empty 483 | # 484 | # blend_chars = +, &, U+23 485 | 486 | 487 | # blended token indexing mode 488 | # a comma separated list of blended token indexing variants 489 | # known variants are trim_none, trim_head, trim_tail, trim_both, skip_pure 490 | # optional, default is trim_none 491 | # 492 | # blend_mode = trim_tail, skip_pure 493 | 494 | 495 | # whether to strip HTML tags from incoming documents 496 | # known values are 0 (do not strip) and 1 (do strip) 497 | # optional, default is 0 498 | html_strip = 0 499 | 500 | # what HTML attributes to index if stripping HTML 501 | # optional, default is empty (do not index anything) 502 | # 503 | # html_index_attrs = img=alt,title; a=title; 504 | 505 | 506 | # what HTML elements contents to strip 507 | # optional, default is empty (do not strip element contents) 508 | # 509 | # html_remove_elements = style, script 510 | 511 | 512 | # whether to preopen index data files on startup 513 | # optional, default is 0 (do not preopen), searchd-only 514 | # 515 | # preopen = 1 516 | 517 | 518 | # whether to enable in-place inversion (2x less disk, 90-95% speed) 519 | # optional, default is 0 (use separate temporary files), indexer-only 520 | # 521 | # inplace_enable = 1 522 | 523 | 524 | # in-place fine-tuning options 525 | # optional, defaults are listed below 526 | # 527 | # inplace_hit_gap = 0 # preallocated hitlist gap size 528 | # inplace_docinfo_gap = 0 # preallocated docinfo gap size 529 | # inplace_reloc_factor = 0.1 # relocation buffer size within arena 530 | # inplace_write_factor = 0.1 # write buffer size within arena 531 | 532 | 533 | # whether to index original keywords along with stemmed versions 534 | # enables "=exactform" operator to work 535 | # optional, default is 0 536 | # 537 | # index_exact_words = 1 538 | 539 | 540 | # position increment on overshort (less that min_word_len) words 541 | # optional, allowed values are 0 and 1, default is 1 542 | # 543 | # overshort_step = 1 544 | 545 | 546 | # position increment on stopword 547 | # optional, allowed values are 0 and 1, default is 1 548 | # 549 | # stopword_step = 1 550 | 551 | 552 | # hitless words list 553 | # positions for these keywords will not be stored in the index 554 | # optional, allowed values are 'all', or a list file name 555 | # 556 | # hitless_words = all 557 | # hitless_words = hitless.txt 558 | 559 | 560 | # detect and index sentence and paragraph boundaries 561 | # required for the SENTENCE and PARAGRAPH operators to work 562 | # optional, allowed values are 0 and 1, default is 0 563 | # 564 | # index_sp = 1 565 | 566 | 567 | # index zones, delimited by HTML/XML tags 568 | # a comma separated list of tags and wildcards 569 | # required for the ZONE operator to work 570 | # optional, default is empty string (do not index zones) 571 | # 572 | # index_zones = title, h*, th 573 | 574 | 575 | # index per-document and average per-index field lengths, in tokens 576 | # required for the BM25A(), BM25F() in expression ranker 577 | # optional, default is 0 (do not index field lenghts) 578 | # 579 | # index_field_lengths = 1 580 | 581 | 582 | # regular expressions (regexps) to filter the fields and queries with 583 | # gets applied to data source fields when indexing 584 | # gets applied to search queries when searching 585 | # multi-value, optional, default is empty list of regexps 586 | # 587 | # regexp_filter = \b(\d+)\" => \1inch 588 | # regexp_filter = (blue|red) => color 589 | 590 | 591 | # list of the words considered frequent with respect to bigram indexing 592 | # optional, default is empty 593 | # 594 | # bigram_freq_words = the, a, i, you, my 595 | 596 | 597 | # bigram indexing mode 598 | # known values are none, all, first_freq, both_freq 599 | # option, default is none (do not index bigrams) 600 | # 601 | # bigram_index = both_freq 602 | 603 | 604 | # snippet document file name prefix 605 | # preprended to file names when generating snippets using load_files option 606 | # WARNING, this is a prefix (not a path), trailing slash matters! 607 | # optional, default is empty 608 | # 609 | # snippets_file_prefix = /mnt/mydocs/server1 610 | 611 | 612 | # whether to apply stopwords before or after stemming 613 | # optional, default is 0 (apply stopwords after stemming) 614 | # 615 | # stopwords_unstemmed = 0 616 | 617 | 618 | # path to a global (cluster-wide) keyword IDFs file 619 | # optional, default is empty (use local IDFs) 620 | # 621 | # global_idf = /usr/local/sphinx/var/global.idf 622 | } 623 | 624 | 625 | # inherited index example 626 | # 627 | # all the parameters are copied from the parent index, 628 | # and may then be overridden in this index definition 629 | index test1stemmed : test1 630 | { 631 | path = /var/data/test1stemmed 632 | morphology = stem_en 633 | } 634 | 635 | 636 | # distributed index example 637 | # 638 | # this is a virtual index which can NOT be directly indexed, 639 | # and only contains references to other local and/or remote indexes 640 | index dist1 641 | { 642 | # 'distributed' index type MUST be specified 643 | type = distributed 644 | 645 | # local index to be searched 646 | # there can be many local indexes configured 647 | local = test1 648 | local = test1stemmed 649 | 650 | # remote agent 651 | # multiple remote agents may be specified 652 | # syntax for TCP connections is 'hostname:port:index1,[index2[,...]]' 653 | # syntax for local UNIX connections is '/path/to/socket:index1,[index2[,...]]' 654 | agent = localhost:9313:remote1 655 | agent = localhost:9314:remote2,remote3 656 | # agent = /var/run/searchd.sock:remote4 657 | 658 | # remote agent mirrors groups, aka mirrors, aka HA agents 659 | # defines 2 or more interchangeable mirrors for a given index part 660 | # 661 | # agent = server3:9312 | server4:9312 :indexchunk2 662 | # agent = server3:9312:chunk2server3 | server4:9312:chunk2server4 663 | # agent = server3:chunk2server3 | server4:chunk2server4 664 | # agent = server21|server22|server23:chunk2 665 | 666 | 667 | # blackhole remote agent, for debugging/testing 668 | # network errors and search results will be ignored 669 | # 670 | # agent_blackhole = testbox:9312:testindex1,testindex2 671 | 672 | 673 | # persistenly connected remote agent 674 | # reduces connect() pressure, requires that workers IS threads 675 | # 676 | # agent_persistent = testbox:9312:testindex1,testindex2 677 | 678 | 679 | # remote agent connection timeout, milliseconds 680 | # optional, default is 1000 ms, ie. 1 sec 681 | agent_connect_timeout = 1000 682 | 683 | # remote agent query timeout, milliseconds 684 | # optional, default is 3000 ms, ie. 3 sec 685 | agent_query_timeout = 3000 686 | 687 | # HA mirror agent strategy 688 | # optional, defaults to ??? (random mirror) 689 | # know values are nodeads, noerrors, roundrobin, nodeadstm, noerrorstm 690 | # 691 | # ha_strategy = nodeads 692 | } 693 | 694 | 695 | # realtime index example 696 | # 697 | # you can run INSERT, REPLACE, and DELETE on this index on the fly 698 | # using MySQL protocol (see 'listen' directive below) 699 | index rt 700 | { 701 | # 'rt' index type must be specified to use RT index 702 | type = rt 703 | 704 | # index files path and file name, without extension 705 | # mandatory, path must be writable, extensions will be auto-appended 706 | path = /var/data/rt 707 | 708 | # RAM chunk size limit 709 | # RT index will keep at most this much data in RAM, then flush to disk 710 | # optional, default is 128M 711 | # 712 | # rt_mem_limit = 512M 713 | 714 | # full-text field declaration 715 | # multi-value, mandatory 716 | rt_field = title 717 | rt_field = content 718 | 719 | # unsigned integer attribute declaration 720 | # multi-value (an arbitrary number of attributes is allowed), optional 721 | # declares an unsigned 32-bit attribute 722 | rt_attr_uint = gid 723 | 724 | # RT indexes currently support the following attribute types: 725 | # uint, bigint, float, timestamp, string, mva, mva64, json 726 | # 727 | # rt_attr_bigint = guid 728 | # rt_attr_float = gpa 729 | # rt_attr_timestamp = ts_added 730 | # rt_attr_string = author 731 | # rt_attr_multi = tags 732 | # rt_attr_multi_64 = tags64 733 | # rt_attr_json = extra_data 734 | } 735 | 736 | ############################################################################# 737 | ## indexer settings 738 | ############################################################################# 739 | 740 | indexer 741 | { 742 | # memory limit, in bytes, kiloytes (16384K) or megabytes (256M) 743 | # optional, default is 128M, max is 2047M, recommended is 256M to 1024M 744 | mem_limit = 128M 745 | 746 | # maximum IO calls per second (for I/O throttling) 747 | # optional, default is 0 (unlimited) 748 | # 749 | # max_iops = 40 750 | 751 | 752 | # maximum IO call size, bytes (for I/O throttling) 753 | # optional, default is 0 (unlimited) 754 | # 755 | # max_iosize = 1048576 756 | 757 | 758 | # maximum xmlpipe2 field length, bytes 759 | # optional, default is 2M 760 | # 761 | # max_xmlpipe2_field = 4M 762 | 763 | 764 | # write buffer size, bytes 765 | # several (currently up to 4) buffers will be allocated 766 | # write buffers are allocated in addition to mem_limit 767 | # optional, default is 1M 768 | # 769 | # write_buffer = 1M 770 | 771 | 772 | # maximum file field adaptive buffer size 773 | # optional, default is 8M, minimum is 1M 774 | # 775 | # max_file_field_buffer = 32M 776 | 777 | 778 | # how to handle IO errors in file fields 779 | # known values are 'ignore_field', 'skip_document', and 'fail_index' 780 | # optional, default is 'ignore_field' 781 | # 782 | # on_file_field_error = skip_document 783 | 784 | 785 | # lemmatizer cache size 786 | # improves the indexing time when the lemmatization is enabled 787 | # optional, default is 256K 788 | # 789 | # lemmatizer_cache = 512M 790 | } 791 | 792 | ############################################################################# 793 | ## searchd settings 794 | ############################################################################# 795 | 796 | searchd 797 | { 798 | # [hostname:]port[:protocol], or /unix/socket/path to listen on 799 | # known protocols are 'sphinx' (SphinxAPI) and 'mysql41' (SphinxQL) 800 | # 801 | # multi-value, multiple listen points are allowed 802 | # optional, defaults are 9312:sphinx and 9306:mysql41, as below 803 | # 804 | # listen = 127.0.0.1 805 | # listen = 192.168.0.1:9312 806 | # listen = 9312 807 | # listen = /var/run/searchd.sock 808 | listen = 9312 809 | listen = 9306:mysql41 810 | 811 | # log file, searchd run info is logged here 812 | # optional, default is 'searchd.log' 813 | log = /var/log/searchd.log 814 | 815 | # query log file, all search queries are logged here 816 | # optional, default is empty (do not log queries) 817 | query_log = /var/log/query.log 818 | 819 | # client read timeout, seconds 820 | # optional, default is 5 821 | read_timeout = 5 822 | 823 | # request timeout, seconds 824 | # optional, default is 5 minutes 825 | client_timeout = 300 826 | 827 | # maximum amount of children to fork (concurrent searches to run) 828 | # optional, default is 0 (unlimited) 829 | max_children = 30 830 | 831 | # maximum amount of persistent connections from this master to each agent host 832 | # optional, but necessary if you use agent_persistent. It is reasonable to set the value 833 | # as max_children, or less on the agent's hosts. 834 | persistent_connections_limit = 30 835 | 836 | # PID file, searchd process ID file name 837 | # mandatory 838 | pid_file = /var/log/searchd.pid 839 | 840 | # seamless rotate, prevents rotate stalls if precaching huge datasets 841 | # optional, default is 1 842 | seamless_rotate = 1 843 | 844 | # whether to forcibly preopen all indexes on startup 845 | # optional, default is 1 (preopen everything) 846 | preopen_indexes = 1 847 | 848 | # whether to unlink .old index copies on succesful rotation. 849 | # optional, default is 1 (do unlink) 850 | unlink_old = 1 851 | 852 | # attribute updates periodic flush timeout, seconds 853 | # updates will be automatically dumped to disk this frequently 854 | # optional, default is 0 (disable periodic flush) 855 | # 856 | # attr_flush_period = 900 857 | 858 | 859 | # MVA updates pool size 860 | # shared between all instances of searchd, disables attr flushes! 861 | # optional, default size is 1M 862 | mva_updates_pool = 1M 863 | 864 | # max allowed network packet size 865 | # limits both query packets from clients, and responses from agents 866 | # optional, default size is 8M 867 | max_packet_size = 8M 868 | 869 | # max allowed per-query filter count 870 | # optional, default is 256 871 | max_filters = 256 872 | 873 | # max allowed per-filter values count 874 | # optional, default is 4096 875 | max_filter_values = 4096 876 | 877 | 878 | # socket listen queue length 879 | # optional, default is 5 880 | # 881 | # listen_backlog = 5 882 | 883 | 884 | # per-keyword read buffer size 885 | # optional, default is 256K 886 | # 887 | # read_buffer = 256K 888 | 889 | 890 | # unhinted read size (currently used when reading hits) 891 | # optional, default is 32K 892 | # 893 | # read_unhinted = 32K 894 | 895 | 896 | # max allowed per-batch query count (aka multi-query count) 897 | # optional, default is 32 898 | max_batch_queries = 32 899 | 900 | 901 | # max common subtree document cache size, per-query 902 | # optional, default is 0 (disable subtree optimization) 903 | # 904 | # subtree_docs_cache = 4M 905 | 906 | 907 | # max common subtree hit cache size, per-query 908 | # optional, default is 0 (disable subtree optimization) 909 | # 910 | # subtree_hits_cache = 8M 911 | 912 | 913 | # multi-processing mode (MPM) 914 | # known values are none, fork, prefork, and threads 915 | # threads is required for RT backend to work 916 | # optional, default is threads 917 | workers = threads # for RT to work 918 | 919 | 920 | # max threads to create for searching local parts of a distributed index 921 | # optional, default is 0, which means disable multi-threaded searching 922 | # should work with all MPMs (ie. does NOT require workers=threads) 923 | # 924 | # dist_threads = 4 925 | 926 | 927 | # binlog files path; use empty string to disable binlog 928 | # optional, default is build-time configured data directory 929 | # 930 | # binlog_path = # disable logging 931 | # binlog_path = /var/data # binlog.001 etc will be created there 932 | 933 | 934 | # binlog flush/sync mode 935 | # 0 means flush and sync every second 936 | # 1 means flush and sync every transaction 937 | # 2 means flush every transaction, sync every second 938 | # optional, default is 2 939 | # 940 | # binlog_flush = 2 941 | 942 | 943 | # binlog per-file size limit 944 | # optional, default is 128M, 0 means no limit 945 | # 946 | # binlog_max_log_size = 256M 947 | 948 | 949 | # per-thread stack size, only affects workers=threads mode 950 | # optional, default is 64K 951 | # 952 | # thread_stack = 128K 953 | 954 | 955 | # per-keyword expansion limit (for dict=keywords prefix searches) 956 | # optional, default is 0 (no limit) 957 | # 958 | # expansion_limit = 1000 959 | 960 | 961 | # RT RAM chunks flush period 962 | # optional, default is 0 (no periodic flush) 963 | # 964 | # rt_flush_period = 900 965 | 966 | 967 | # query log file format 968 | # optional, known values are plain and sphinxql, default is plain 969 | # 970 | # query_log_format = sphinxql 971 | 972 | 973 | # version string returned to MySQL network protocol clients 974 | # optional, default is empty (use Sphinx version) 975 | # 976 | # mysql_version_string = 5.0.37 977 | 978 | 979 | # default server-wide collation 980 | # optional, default is libc_ci 981 | # 982 | # collation_server = utf8_general_ci 983 | 984 | 985 | # server-wide locale for libc based collations 986 | # optional, default is C 987 | # 988 | # collation_libc_locale = ru_RU.UTF-8 989 | 990 | 991 | # threaded server watchdog (only used in workers=threads mode) 992 | # optional, values are 0 and 1, default is 1 (watchdog on) 993 | # 994 | # watchdog = 1 995 | 996 | 997 | # costs for max_predicted_time model, in (imaginary) nanoseconds 998 | # optional, default is "doc=64, hit=48, skip=2048, match=64" 999 | # 1000 | # predicted_time_costs = doc=64, hit=48, skip=2048, match=64 1001 | 1002 | 1003 | # current SphinxQL state (uservars etc) serialization path 1004 | # optional, default is none (do not serialize SphinxQL state) 1005 | # 1006 | # sphinxql_state = sphinxvars.sql 1007 | 1008 | 1009 | # maximum RT merge thread IO calls per second, and per-call IO size 1010 | # useful for throttling (the background) OPTIMIZE INDEX impact 1011 | # optional, default is 0 (unlimited) 1012 | # 1013 | # rt_merge_iops = 40 1014 | # rt_merge_maxiosize = 1M 1015 | 1016 | 1017 | # interval between agent mirror pings, in milliseconds 1018 | # 0 means disable pings 1019 | # optional, default is 1000 1020 | # 1021 | # ha_ping_interval = 0 1022 | 1023 | 1024 | # agent mirror statistics window size, in seconds 1025 | # stats older than the window size (karma) are retired 1026 | # that is, they will not affect master choice of agents in any way 1027 | # optional, default is 60 seconds 1028 | # 1029 | # ha_period_karma = 60 1030 | 1031 | 1032 | # delay between preforked children restarts on rotation, in milliseconds 1033 | # optional, default is 0 (no delay) 1034 | # 1035 | # prefork_rotation_throttle = 100 1036 | 1037 | 1038 | # a prefix to prepend to the local file names when creating snippets 1039 | # with load_files and/or load_files_scatter options 1040 | # optional, default is empty 1041 | # 1042 | # snippets_file_prefix = /mnt/common/server1/ 1043 | } 1044 | 1045 | ############################################################################# 1046 | ## common settings 1047 | ############################################################################# 1048 | 1049 | common 1050 | { 1051 | 1052 | # lemmatizer dictionaries base path 1053 | # optional, defaut is /usr/local/share (see ./configure --datadir) 1054 | # 1055 | # lemmatizer_base = /usr/local/share/sphinx/dicts 1056 | 1057 | 1058 | # how to handle syntax errors in JSON attributes 1059 | # known values are 'ignore_attr' and 'fail_index' 1060 | # optional, default is 'ignore_attr' 1061 | # 1062 | # on_json_attr_error = fail_index 1063 | 1064 | 1065 | # whether to auto-convert numeric values from strings in JSON attributes 1066 | # with auto-conversion, string value with actually numeric data 1067 | # (as in {"key":"12345"}) gets stored as a number, rather than string 1068 | # optional, allowed values are 0 and 1, default is 0 (do not convert) 1069 | # 1070 | # json_autoconv_numbers = 1 1071 | 1072 | 1073 | # whether and how to auto-convert key names in JSON attributes 1074 | # known value is 'lowercase' 1075 | # optional, default is unspecified (do nothing) 1076 | # 1077 | # json_autoconv_keynames = lowercase 1078 | 1079 | 1080 | # trusted plugin directory 1081 | # optional, default is empty (disable UDFs) 1082 | # 1083 | # plugin_dir = /usr/local/sphinx/lib 1084 | 1085 | } 1086 | 1087 | # --eof-- 1088 | -------------------------------------------------------------------------------- /sphinx/etc/uni.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asphxg/findpass/5a6708bda5be5cac1837e10f7a072ed3d9e2f03d/sphinx/etc/uni.lib -------------------------------------------------------------------------------- /sphinx/misc/raminfo.py: -------------------------------------------------------------------------------- 1 | # a simple tool to examine sphinx rt index .ram file 2 | 3 | import sys, struct, os 4 | 5 | 6 | def get_dword(f): 7 | return struct.unpack('I', f.read(4))[0] 8 | 9 | 10 | def get_id(f, id64): 11 | if id64: 12 | return struct.unpack('Q', f.read(8))[0] 13 | else: 14 | return struct.unpack('I', f.read(4))[0] 15 | 16 | 17 | def skip_vec(f, entry_size): 18 | f.seek(get_dword(f) * entry_size, 1) 19 | 20 | 21 | def main(argv): 22 | if len(argv) < 2: 23 | sys.stderr.write("usage: raminfo.py ") 24 | return 1 25 | 26 | if not os.path.exists(argv[1]): 27 | sys.stderr.write("ERROR: file %r not found!" % argv[1]) 28 | return 1 29 | 30 | f = open(argv[1], "rb") 31 | print "examining RAM chunk %r..." % argv[1] 32 | id64 = get_dword(f) 33 | if id64!=0: 34 | id64 = 1 35 | next_tag = get_dword(f) 36 | num_segments = get_dword(f) 37 | print "%d segments total, %d-bit ids, next free tag %d" % (num_segments, 32*(1+id64), next_tag) 38 | 39 | if num_segments>32: 40 | print "WARNING, unexpectedly many segments, displaying first 32 only" 41 | 42 | index_minid = 0 43 | index_maxid = 0 44 | total_rows = 0 45 | total_alive = 0 46 | 47 | for i in range(1, num_segments+1): 48 | off1 = f.tell() 49 | tag = get_dword(f) 50 | skip_vec(f, 1) # keywords 51 | skip_vec(f, 1) # checkpoints1 52 | skip_vec(f, 16) # checkpoints2 53 | skip_vec(f, 1) # docs 54 | skip_vec(f, 1) # hits 55 | 56 | rows = get_dword(f) 57 | alive = get_dword(f) 58 | off2 = f.tell() 59 | skip_vec(f, 4) # rows data 60 | off3 = f.tell() 61 | 62 | rowsize = (off3 - off2 - 4)/rows 63 | f.seek(off2 + 4) 64 | minid = get_id(f, id64) 65 | f.seek(off3 - rowsize) 66 | maxid = get_id(f, id64) 67 | f.seek(off3) 68 | 69 | skip_vec(f, 4*(1+id64)) # klist 70 | skip_vec(f, 1) # strings 71 | skip_vec(f, 4) # mva 72 | skip_vec(f, 8) # infixcp 73 | 74 | xrows = "" 75 | if rows!=alive: 76 | xrows = " (alive %d)" % alive 77 | 78 | if i<=32: 79 | print "seg %d at off %d, tag %d, rows %d%s at off %d, %d b/row, ids %d..%d" % (i, off1, tag, rows, xrows, off2, rowsize, minid, maxid) 80 | else: 81 | if sys.stdout.isatty() and (i % 1000)==0: 82 | sys.stdout.write("scanning segment %d of %d\r" % (i, num_segments)) 83 | 84 | if index_maxid==0: 85 | index_minid = minid 86 | index_maxid = maxid 87 | else: 88 | index_minid = min(index_minid, minid) 89 | index_maxid = max(index_maxid, maxid) 90 | total_rows += rows 91 | total_alive += alive 92 | 93 | print "total %d rows (%d alive), ids %d..%d" % (total_rows, total_alive, index_minid, index_maxid) 94 | 95 | if __name__ == "__main__": 96 | sys.exit(main(sys.argv)) 97 | -------------------------------------------------------------------------------- /sphinx/misc/resolve.py: -------------------------------------------------------------------------------- 1 | # 2 | # $Id$ 3 | # 4 | 5 | import sys, re 6 | 7 | if len(sys.argv)!=3: 8 | print 'Usage: python resolve.py BACKTRACE SYMBOLS' 9 | sys.exit(0) 10 | 11 | def myopen(name): 12 | if name == '-': 13 | return sys.stdin 14 | fh = open(name, 'r') 15 | if not fh: 16 | print 'FATAL: failed to open %s' % name 17 | sys.exit(1) 18 | return fh 19 | 20 | syms = [] 21 | fp = myopen(sys.argv[2]) 22 | for line in fp.readlines(): 23 | line = line.rstrip() 24 | match = re.match('([0-9a-fA-F]+) \w ', line) 25 | if match: 26 | addr = int(match.group(1), 16) 27 | name = line[len(match.group(0)):] 28 | syms.append([addr, name]) 29 | fp.close() 30 | 31 | fp = myopen(sys.argv[1]) 32 | for line in fp.readlines(): 33 | line = line.rstrip() 34 | 35 | # skip plain boring log entries 36 | if re.search('^\[\w+\s+\w+\s+\d+\s+\d+:\d+:\d+\.\d+ \d+\] \[\d+\] \S', line): 37 | continue 38 | 39 | # resolve symbols, if any 40 | match = re.search('\[0x([0-9a-fA-F]+)\]', line) 41 | if match: 42 | addr = int(match.group(1), 16) 43 | resolved = '???' 44 | for i in range(len(syms)-1): 45 | if syms[i][0]<=addr and addr -------------------------------------------------------------------------------- /sphinx/misc/searchd: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Example init file for searchd 4 | # 5 | # chkconfig: 2345 55 25 6 | # 7 | # description: searchd 8 | # 9 | # USE "chkconfig --add searchd" to configure Sphinx searchd service 10 | # 11 | # by Vladimir Fedorkov Mar 1, 2006, info@astellar.com 12 | # public domain 13 | 14 | SUDO_USER=searchd 15 | 16 | BASE_PATH=/release/search 17 | PID_FILE=$BASE_PATH/searchd.pid 18 | CONFIG_FILE=$BASE_PATH/sphinx.conf 19 | 20 | EXEC_PATH=$BASE_PATH 21 | LOG_PATH=$EXEC_PATH 22 | 23 | RETVAL=0 24 | prog="searchd" 25 | 26 | do_config() { 27 | mkdir -p $EXEC_PATH 28 | mkdir $EXEC_PATH/data 29 | mkdir -p $LOG_PATH 30 | chown -R $SUDO_USER $EXEC_PATH 31 | chown -R $SUDO_USER $EXEC_PATH/$CONFIG_FILE 32 | chown -R $SUDO_USER $LOG_PATH 33 | 34 | chmod 600 $EXEC_PATH/$CONFIG_FILE 35 | chmod u+rwx $EXEC_PATH/* 36 | chmod -R u+rw,go-rwx $EXEC_PATH/data 37 | chmod -R u+rw,go-rwx $LOG_PATH 38 | } 39 | 40 | do_start() { 41 | echo "Starting $prog" 42 | sudo -u $SUDO_USER $EXEC_PATH/$prog --config $CONFIG_FILE 43 | RETVAL=$? 44 | echo 45 | return $RETVAL 46 | } 47 | 48 | do_stop() { 49 | echo "Stopping $prog" 50 | if [ -e $PID_FILE ] ; then 51 | kill -15 `cat $PID_FILE` 52 | sleep 5 53 | if [ -e $PID_FILE ] ; then 54 | kill -9 `cat $PID_FILE` 55 | fi 56 | fi 57 | RETVAL=$? 58 | echo 59 | return $RETVAL 60 | } 61 | 62 | case $* in 63 | 64 | config) 65 | do_config 66 | ;; 67 | 68 | start) 69 | do_start 70 | ;; 71 | 72 | stop) 73 | do_stop 74 | ;; 75 | 76 | *) 77 | echo "usage: $0 {start|stop|config}" >&2 78 | 79 | exit 1 80 | ;; 81 | esac 82 | 83 | exit $RETVAL 84 | -------------------------------------------------------------------------------- /sphinx/misc/suggest/README: -------------------------------------------------------------------------------- 1 | Suggestions sample 2 | ------------------- 3 | 4 | 0) What's this. This sample shows how to implement simple keyword 5 | correction suggestions (ie. "did you mean") using Sphinx. 6 | 7 | 1) Requirements. You will need Sphinx, MySQL, and PHP CLI. 8 | 9 | 2) Quickstart. (Skip first indexer command to use bundled sample.) 10 | 11 | indexer YOURINDEX --config YOURCONFIG.CONF --buildstops dict.txt 100000 --buildfreqs 12 | cat dict.txt | php suggest.php --builddict > dict.sql 13 | mysql -u root test < dict.sql 14 | indexer --config suggest.conf --all 15 | searchd --config suggest.conf 16 | php suggest.php --query sphynx 17 | 18 | --eof-- 19 | -------------------------------------------------------------------------------- /sphinx/misc/suggest/suggest.conf: -------------------------------------------------------------------------------- 1 | source suggest 2 | { 3 | type = mysql 4 | sql_host = localhost 5 | sql_user = root 6 | sql_pass = 7 | sql_db = test 8 | 9 | sql_query_pre = SET NAMES utf8 10 | sql_query = SELECT id, trigrams, freq, LENGTH(keyword) AS len, keyword FROM suggest 11 | 12 | sql_attr_uint = freq 13 | sql_attr_uint = len 14 | sql_attr_string = keyword 15 | } 16 | 17 | 18 | index suggest 19 | { 20 | source = suggest 21 | path = suggest 22 | docinfo = extern 23 | charset_type = utf-8 24 | } 25 | 26 | 27 | indexer 28 | { 29 | mem_limit = 32M 30 | } 31 | 32 | 33 | searchd 34 | { 35 | log = searchd.log 36 | query_log = query.log 37 | read_timeout = 5 38 | max_children = 30 39 | pid_file = searchd.pid 40 | max_matches = 1000 41 | seamless_rotate = 1 42 | preopen_indexes = 0 43 | unlink_old = 1 44 | } 45 | -------------------------------------------------------------------------------- /sphinx/misc/suggest/suggest.php: -------------------------------------------------------------------------------- 1 | SetRankingMode ( SPH_RANK_WORDCOUNT ); 85 | $cl->SetFilterRange ( "len", $len-$delta, $len+$delta ); 86 | $cl->SetSelect ( "*, @weight+$delta-abs(len-$len) AS myrank" ); 87 | $cl->SetSortMode ( SPH_SORT_EXTENDED, "myrank DESC, freq DESC" ); 88 | $cl->SetArrayResult ( true ); 89 | 90 | // pull top-N best trigram matches and run them through Levenshtein 91 | $cl->SetLimits ( 0, TOP_COUNT ); 92 | $res = $cl->Query ( $query, "suggest" ); 93 | 94 | if ( !$res || !$res["matches"] ) 95 | return false; 96 | 97 | if ( SUGGEST_DEBUG ) 98 | { 99 | print "--- DEBUG START ---\n"; 100 | 101 | foreach ( $res["matches"] as $match ) 102 | { 103 | $w = $match["attrs"]["keyword"]; 104 | $myrank = @$match["attrs"]["myrank"]; 105 | if ( $myrank ) 106 | $myrank = ", myrank=$myrank"; 107 | 108 | // FIXME? add costs? 109 | // FIXME! does not work with UTF-8.. THIS! IS!! PHP!!! 110 | $levdist = levenshtein ( $keyword, $w ); 111 | 112 | print "id=$match[id], weight=$match[weight], freq={$match[attrs][freq]}{$myrank}, word=$w, levdist=$levdist\n"; 113 | } 114 | 115 | print "--- DEBUG END ---\n"; 116 | } 117 | 118 | // further restrict trigram matches with a sane Levenshtein distance limit 119 | foreach ( $res["matches"] as $match ) 120 | { 121 | $suggested = $match["attrs"]["keyword"]; 122 | if ( levenshtein ( $keyword, $suggested )<=LEVENSHTEIN_THRESHOLD ) 123 | return $suggested; 124 | } 125 | return $keyword; 126 | } 127 | 128 | /// main 129 | if ( $_SERVER["argc"]<2 ) 130 | { 131 | die ( "usage:\n" 132 | . "php suggest.php --builddict\treads stopwords from stdin, prints SQL dump of the dictionary to stdout\n" 133 | . "php suggest.php --query WORD\tqueries Sphinx, prints suggestion\n" ); 134 | } 135 | 136 | if ( $_SERVER["argv"][1]=="--builddict" ) 137 | { 138 | $in = fopen ( "php://stdin", "r" ); 139 | $out = fopen ( "php://stdout", "w+" ); 140 | BuildDictionarySQL ( $out, $in ); 141 | } 142 | 143 | if ( $_SERVER["argv"][1]=="--query" ) 144 | { 145 | mysql_connect ( "localhost", "root", "" ) or die ( "mysql_connect() failed: ".mysql_error() ); 146 | mysql_select_db ( "test" ) or die ( "mysql_select_db() failed: ".mysql_error() ); 147 | 148 | $keyword = $_SERVER["argv"][2]; 149 | printf ( "keyword: %s\nsuggestion: %s\n", $keyword, MakeSuggestion($keyword) ); 150 | } 151 | -------------------------------------------------------------------------------- /sphinx/misc/wordbreak.pl: -------------------------------------------------------------------------------- 1 | # 2 | # prepare titles for wordbreaker frequency dictionary builder 3 | # extract and cleanup data 4 | # 5 | 6 | # 7 | # usage example: 8 | # 9 | # perl wordbreak.pl < raw.xml > titles.xml 10 | # indexer ub --buildstops titles-freq.txt 10000000 --buildfreqs 11 | # 12 | # sphinx.conf: 13 | # 14 | # source ub 15 | # { 16 | # type = xmlpipe2 17 | # xmlpipe_field = title 18 | # xmlpipe_fixup_utf8 = 1 19 | # xmlpipe_command = cat titles.xml 20 | # } 21 | # 22 | # index ub 23 | # { 24 | # dict = keywords 25 | # type = plain 26 | # source = ub 27 | # path = ub 28 | # charset_type = utf-8 29 | # html_strip = 0 30 | # charset_table = A..Z->a..z, a..z 31 | # } 32 | # 33 | 34 | $n = 1; 35 | print "\n"; 36 | print "\n"; 37 | while (<>) 38 | { 39 | # extract title 40 | next if (!/^\s*/); 41 | chomp; 42 | 43 | # cleanup ABC's as in World's 44 | s/[a-z]\'s\b//ig; 45 | 46 | # cleanup A.B.C. as in S.r.l. and other abbreviations 47 | s/\b([a-z]\.){2,}\b//ig; 48 | 49 | # cleanup A&B as in H&M 50 | s/\b[a-z]\&[a-z]\b//ig; 51 | 52 | # cleanup ABC.com as in google.com, brisbanetimes.com.au, etc 53 | s/\b\w+(\.(com|org|net))*\.(com|org|net|it|de|pl|co\.uk|nl|edu|eu|info|fr|ch|br|ru|at|ca|si|tv|es|gov|br|au|jp|biz|dk|il|se|cz|no)\b//ig; 54 | 55 | # print out cleaned up document 56 | print "<sphinx:document id=\"$n\">"; 57 | print; 58 | print "</sphinx:document>\n"; 59 | $n++; 60 | } 61 | print "</sphinx:docset>\n"; 62 | -------------------------------------------------------------------------------- /sphinx/src/sphinxudf.c: -------------------------------------------------------------------------------- 1 | // 2 | // $Id$ 3 | // 4 | 5 | // 6 | // Copyright (c) 2011-2016, Andrew Aksyonoff 7 | // Copyright (c) 2011-2016, Sphinx Technologies Inc 8 | // All rights reserved 9 | // 10 | // This program is free software; you can redistribute it and/or modify 11 | // it under the terms of the GNU General Public License. You should have 12 | // received a copy of the GPL license along with this program; if you 13 | // did not, you can find it at http://www.gnu.org/ 14 | // 15 | 16 | // 17 | // Sphinx UDF helpers implementation 18 | // 19 | 20 | #include "sphinxudf.h" 21 | 22 | #include <memory.h> 23 | #include <stdlib.h> 24 | 25 | #define SPH_UDF_MAX_FIELD_FACTORS 256 26 | #define SPH_UDF_MAX_TERM_FACTORS 2048 27 | #define SPH_UDF_MAX_FIELD_SIZE 8 ///< how many ints to store a bitmask for 256 fields? 28 | 29 | /// helper function that must be called to initialize the SPH_UDF_FACTORS structure 30 | /// before it is passed to sphinx_factors_unpack 31 | /// returns 0 on success 32 | /// returns an error code on error 33 | int sphinx_factors_init ( SPH_UDF_FACTORS * out ) 34 | { 35 | if ( !out ) 36 | return 1; 37 | 38 | memset ( out, 0, sizeof(SPH_UDF_FACTORS) ); 39 | return 0; 40 | } 41 | 42 | 43 | /// helper function that unpacks PACKEDFACTORS() blob into SPH_UDF_FACTORS structure 44 | /// MUST be in sync with PackFactors() method in sphinxsearch.cpp 45 | /// returns 0 on success 46 | /// returns an error code on error 47 | int sphinx_factors_unpack ( const unsigned int * in, SPH_UDF_FACTORS * out ) 48 | { 49 | const unsigned int * pack = in; 50 | SPH_UDF_FIELD_FACTORS * f; 51 | SPH_UDF_TERM_FACTORS * t; 52 | int i, size, fields, fields_size; 53 | unsigned int exact_hit_mask[SPH_UDF_MAX_FIELD_SIZE]; 54 | unsigned int exact_order_mask[SPH_UDF_MAX_FIELD_SIZE]; 55 | unsigned int exact_field_hit_mask[SPH_UDF_MAX_FIELD_SIZE]; 56 | unsigned int full_field_hit_mask[SPH_UDF_MAX_FIELD_SIZE]; 57 | 58 | if ( !in || !out ) 59 | return 1; 60 | 61 | if ( out->field || out->term ) 62 | return 1; 63 | 64 | // extract size, extract document-level factors 65 | size = *in++; 66 | 67 | out->doc_bm15 = *in++; 68 | out->doc_bm25a = *(float*)in++; 69 | out->matched_fields = *in++; 70 | out->doc_word_count = *in++; 71 | out->num_fields = *in++; 72 | 73 | // extract field-level factors 74 | if ( out->num_fields > SPH_UDF_MAX_FIELD_FACTORS ) 75 | return 1; 76 | 77 | fields_size = ( out->num_fields + 31 ) / 32; 78 | 79 | for ( i=0; i<fields_size; i++ ) 80 | exact_hit_mask[i] = *in++; 81 | 82 | for ( i=0; i<fields_size; i++ ) 83 | exact_order_mask[i] = *in++; 84 | 85 | for ( i=0; i<fields_size; i++ ) 86 | exact_field_hit_mask[i] = *in++; 87 | 88 | for ( i=0; i<fields_size; i++ ) 89 | full_field_hit_mask[i] = *in++; 90 | 91 | if ( out->num_fields > 0 ) 92 | { 93 | i = out->num_fields*sizeof(SPH_UDF_FIELD_FACTORS); 94 | out->field = (SPH_UDF_FIELD_FACTORS*) malloc ( i ); 95 | memset ( out->field, 0, i ); 96 | } 97 | 98 | for ( i=0; i<out->num_fields; i++ ) 99 | { 100 | f = &(out->field[i]); 101 | f->hit_count = *in++; 102 | 103 | if ( f->hit_count ) 104 | { 105 | f->id = *in++; 106 | f->lcs = *in++; 107 | f->word_count = *in++; 108 | f->tf_idf = *(float*)in++; 109 | f->min_idf = *(float*)in++; 110 | f->max_idf = *(float*)in++; 111 | f->sum_idf = *(float*)in++; 112 | f->min_hit_pos = (int)*in++; 113 | f->min_best_span_pos = (int)*in++; 114 | f->max_window_hits = (int)*in++; 115 | f->min_gaps = (int)*in++; 116 | f->atc = *(float*)in++; 117 | f->lccs = *in++; 118 | f->wlccs = *(float*)in++; 119 | f->exact_hit = (char)( ( exact_hit_mask [ i>>5 ] & ( 1UL<<( i&31 ) ) )!=0 ); 120 | f->exact_order = (char)( ( exact_order_mask [ i>>5 ] & ( 1UL<<( i&31 ) ) )!=0 ); 121 | f->exact_field_hit = (char)( ( exact_field_hit_mask [ i>>5 ] & ( 1UL<<( i&31 ) ) )!=0 ); 122 | f->full_field_hit = (char)( ( full_field_hit_mask [ i>>5 ] & ( 1UL<<( i&31 ) ) )!=0 ); 123 | } else 124 | { 125 | // everything else is already zeroed out by memset() above 126 | f->id = i; 127 | } 128 | } 129 | 130 | // extract term-level factors 131 | out->max_uniq_qpos = *in++; 132 | if ( out->max_uniq_qpos > SPH_UDF_MAX_TERM_FACTORS ) 133 | return 1; 134 | 135 | if ( out->max_uniq_qpos > 0 ) 136 | out->term = (SPH_UDF_TERM_FACTORS*) malloc ( out->max_uniq_qpos*sizeof(SPH_UDF_TERM_FACTORS) ); 137 | 138 | for ( i=0; i<out->max_uniq_qpos; i++ ) 139 | { 140 | t = &(out->term[i]); 141 | t->keyword_mask = *in++; 142 | if ( t->keyword_mask ) 143 | { 144 | t->id = *in++; 145 | t->tf = (int)*in++; 146 | t->idf = *(float*)in++; 147 | } 148 | } 149 | 150 | // extract field_tf factors 151 | fields = *in++; 152 | out->field_tf = (int*) malloc ( fields*sizeof(int) ); 153 | memcpy ( out->field_tf, in, fields*sizeof(int) ); 154 | in += fields; 155 | 156 | // do a safety check, and return 157 | return ( size!=( (int)(in-pack) * (int)sizeof(unsigned int) ) ) ? 1 : 0; 158 | } 159 | 160 | 161 | /// helper function that must be called to free the memory allocated by the sphinx_factors_unpack 162 | /// function call 163 | /// returns 0 on success 164 | /// returns an error code on error 165 | int sphinx_factors_deinit ( SPH_UDF_FACTORS * out ) 166 | { 167 | if ( !out ) 168 | return 1; 169 | 170 | free ( out->term ); 171 | free ( out->field ); 172 | free ( out->field_tf ); 173 | 174 | return 0; 175 | } 176 | 177 | ////////////////////////////////////////////////////////////////////////// 178 | 179 | static const unsigned int * skip_fields ( const unsigned int * in, int n ) 180 | { 181 | in += 6 + ( ( in[5] + 31 ) / 32 ) * 4; // skip heading document factors and 4 exact/full masks 182 | while ( n-->0 ) 183 | in += ( in[0]>0 ) ? 15 : 1; // skip 15 ints per matched field, or 1 per unmatched 184 | return in; 185 | } 186 | 187 | 188 | static const unsigned int * skip_terms ( const unsigned int * in, int n ) 189 | { 190 | in += 1; // skip max_uniq_qpos 191 | while ( n-->0 ) 192 | in += ( in[0]>0 ) ? 4 : 1; // skip 4 ints per matched term, or 1 per unmatched 193 | return in; 194 | } 195 | 196 | 197 | const unsigned int * sphinx_get_field_factors ( const unsigned int * in, int field ) 198 | { 199 | if ( !in || field<0 || field>=(int)in[5] ) 200 | return NULL; // blob[5] is num_fields, do a sanity check 201 | in = skip_fields ( in, field ); 202 | if ( !in[0] ) 203 | return NULL; // no hits, no fun 204 | if ( (int)in[1]!=field ) 205 | return NULL; // field[1] is field_id, do a sanity check 206 | return in; // all good 207 | } 208 | 209 | 210 | const unsigned int * sphinx_get_term_factors ( const unsigned int * in, int term ) 211 | { 212 | if ( !in || term<0 ) 213 | return NULL; 214 | in = skip_fields ( in, in[5] ); // skip all fields 215 | if ( term>(int)in[0] ) 216 | return NULL; // sanity check vs max_uniq_qpos ( qpos and terms range - [1, max_uniq_qpos] 217 | in = skip_terms ( in, term-1); 218 | if ( !in[0] ) 219 | return NULL; // unmatched term 220 | if ( (int)in[1]!=term ) 221 | return NULL; // term[1] is keyword_id, sanity check failed 222 | return in; 223 | } 224 | 225 | 226 | int sphinx_get_doc_factor_int ( const unsigned int * in, enum sphinx_doc_factor f ) 227 | { 228 | int fields_size; 229 | switch ( f ) 230 | { 231 | case SPH_DOCF_BM15: return (int)in[1]; 232 | case SPH_DOCF_BM25A: return (int)in[2]; 233 | case SPH_DOCF_MATCHED_FIELDS: return (int)in[3]; 234 | case SPH_DOCF_DOC_WORD_COUNT: return (int)in[4]; 235 | case SPH_DOCF_NUM_FIELDS: return (int)in[5]; 236 | case SPH_DOCF_MAX_UNIQ_QPOS: 237 | in = skip_fields ( in, in[5] ); 238 | return (int)in[0]; 239 | case SPH_DOCF_EXACT_HIT_MASK: return (int)in[6]; 240 | case SPH_DOCF_EXACT_ORDER_MASK: fields_size = ( (int)in[5] + 31 ) / 32; return (int)in[6+fields_size]; 241 | case SPH_DOCF_EXACT_FIELD_HIT_MASK: fields_size = ( (int)in[5] + 31 ) / 32; return (int)in[6+fields_size*2]; 242 | case SPH_DOCF_FULL_FIELD_HIT_MASK: fields_size = ( (int)in[5] + 31 ) / 32; return (int)in[6+fields_size*3]; 243 | } 244 | return 0; 245 | } 246 | 247 | const unsigned int * sphinx_get_doc_factor_ptr ( const unsigned int * in, enum sphinx_doc_factor f ) 248 | { 249 | int fields_size; 250 | 251 | if ( f==SPH_DOCF_EXACT_HIT_MASK ) 252 | return in + 6; 253 | 254 | fields_size = ( (int)in[5] + 31 ) / 32; 255 | if ( f==SPH_DOCF_EXACT_ORDER_MASK ) 256 | return in + 6 + fields_size; 257 | 258 | if ( f==SPH_DOCF_EXACT_FIELD_HIT_MASK ) 259 | return in + 6 + fields_size*2; 260 | 261 | if ( f==SPH_DOCF_FULL_FIELD_HIT_MASK ) 262 | return in + 6 + fields_size*3; 263 | 264 | return 0; 265 | } 266 | 267 | float sphinx_get_doc_factor_float ( const unsigned int * in, enum sphinx_doc_factor f ) 268 | { 269 | if ( f==SPH_DOCF_BM25A ) 270 | return *(float*)&in[2]; 271 | else 272 | return 0.0f; 273 | } 274 | 275 | 276 | int sphinx_get_field_factor_int ( const unsigned int * in, enum sphinx_field_factor f ) 277 | { 278 | if ( !in ) 279 | return 0; 280 | // in[1] is id 281 | switch ( f ) 282 | { 283 | case SPH_FIELDF_HIT_COUNT: return (int)in[0]; 284 | case SPH_FIELDF_LCS: return (int)in[2]; 285 | case SPH_FIELDF_WORD_COUNT: return (int)in[3]; 286 | case SPH_FIELDF_TF_IDF: return (int)in[4]; 287 | case SPH_FIELDF_MIN_IDF: return (int)in[5]; 288 | case SPH_FIELDF_MAX_IDF: return (int)in[6]; 289 | case SPH_FIELDF_SUM_IDF: return (int)in[7]; 290 | case SPH_FIELDF_MIN_HIT_POS: return (int)in[8]; 291 | case SPH_FIELDF_MIN_BEST_SPAN_POS: return (int)in[9]; 292 | case SPH_FIELDF_MAX_WINDOW_HITS: return (int)in[10]; 293 | case SPH_FIELDF_MIN_GAPS: return (int)in[11]; 294 | case SPH_FIELDF_ATC: return (int)in[12]; 295 | case SPH_FIELDF_LCCS: return (int)in[13]; 296 | case SPH_FIELDF_WLCCS: return (int)in[14]; 297 | } 298 | return 0; 299 | } 300 | 301 | 302 | int sphinx_get_term_factor_int ( const unsigned int * in, enum sphinx_term_factor f ) 303 | { 304 | if ( !in ) 305 | return 0; 306 | switch ( f ) 307 | { 308 | case SPH_TERMF_KEYWORD_MASK: return (int)in[0]; 309 | case SPH_TERMF_TF: return (int)in[2]; 310 | case SPH_TERMF_IDF: return (int)in[3]; 311 | } 312 | return 0; 313 | } 314 | 315 | 316 | float sphinx_get_field_factor_float ( const unsigned int * in, enum sphinx_field_factor f ) 317 | { 318 | int r = sphinx_get_field_factor_int ( in, f ); 319 | void * pvoid = &r; 320 | return *(float*)pvoid; 321 | } 322 | 323 | 324 | float sphinx_get_term_factor_float ( const unsigned int * in, enum sphinx_term_factor f ) 325 | { 326 | int r = sphinx_get_term_factor_int ( in, f ); 327 | void * pvoid = &r; 328 | return *(float*)pvoid; 329 | } 330 | 331 | // 332 | // $Id$ 333 | // 334 | -------------------------------------------------------------------------------- /sphinx/src/sphinxudf.h: -------------------------------------------------------------------------------- 1 | // 2 | // $Id$ 3 | // 4 | 5 | // 6 | // Copyright (c) 2011-2016, Andrew Aksyonoff 7 | // Copyright (c) 2011-2016, Sphinx Technologies Inc 8 | // All rights reserved 9 | // 10 | // This program is free software; you can redistribute it and/or modify 11 | // it under the terms of the GNU General Public License. You should have 12 | // received a copy of the GPL license along with this program; if you 13 | // did not, you can find it at http://www.gnu.org/ 14 | // 15 | 16 | // 17 | // Sphinx plugin interface header 18 | // 19 | // This file will be included by plugin implementations, so it should be 20 | // portable plain C, stay standalone, and change as rarely as possible. 21 | // 22 | // Refer to src/udfexample.c for a working UDF example, and refer to 23 | // doc/sphinx.html#extending-sphinx for more information on writing 24 | // plugins and UDFs. 25 | // 26 | 27 | #ifndef _sphinxudf_ 28 | #define _sphinxudf_ 29 | 30 | #ifdef __cplusplus 31 | extern "C" { 32 | #endif 33 | 34 | /// current udf version 35 | #define SPH_UDF_VERSION 12 36 | 37 | /// error buffer size 38 | #define SPH_UDF_ERROR_LEN 256 39 | 40 | ////////////////////////////////////////////////////////////////////////// 41 | // UDF PLUGINS 42 | ////////////////////////////////////////////////////////////////////////// 43 | 44 | /// UDF argument and result value types 45 | enum sphinx_udf_argtype 46 | { 47 | SPH_UDF_TYPE_UINT32 = 1, ///< unsigned 32-bit integer 48 | SPH_UDF_TYPE_UINT32SET = 2, ///< sorted set of unsigned 32-bit integers 49 | SPH_UDF_TYPE_INT64 = 3, ///< signed 64-bit integer 50 | SPH_UDF_TYPE_FLOAT = 4, ///< single-precision IEEE 754 float 51 | SPH_UDF_TYPE_STRING = 5, ///< non-ASCIIZ string, with a separately stored length 52 | SPH_UDF_TYPE_INT64SET = 6, ///< sorted set of signed 64-bit integers 53 | SPH_UDF_TYPE_FACTORS = 7, ///< packed ranking factors 54 | SPH_UDF_TYPE_JSON = 8 ///< whole json or particular field as a string 55 | }; 56 | 57 | /// our malloc() replacement type 58 | /// results that are returned to searchd MUST be allocated using this replacement 59 | typedef void * sphinx_malloc_fn ( int ); 60 | 61 | /// UDF call arguments 62 | typedef struct st_sphinx_udf_args 63 | { 64 | int arg_count; ///< number of arguments 65 | enum sphinx_udf_argtype * arg_types; ///< argument types 66 | char ** arg_values; ///< argument values (strings are not (!) ASCIIZ; see str_lengths below) 67 | char ** arg_names; ///< argument names (ASCIIZ argname in 'expr AS argname' case; NULL otherwise) 68 | int * str_lengths; ///< string argument lengths 69 | sphinx_malloc_fn * fn_malloc; ///< malloc() replacement to allocate returned values 70 | } SPH_UDF_ARGS; 71 | 72 | /// UDF initialization 73 | typedef struct st_sphinx_udf_init 74 | { 75 | void * func_data; ///< function data (will be passed to calls, deinit) 76 | char is_const; ///< whether a function returns a constant 77 | } SPH_UDF_INIT; 78 | 79 | /// integer return types 80 | #if defined(_MSC_VER) || defined(__WIN__) 81 | typedef __int64 sphinx_int64_t; 82 | typedef unsigned __int64 sphinx_uint64_t; 83 | #else 84 | typedef long long sphinx_int64_t; 85 | typedef unsigned long long sphinx_uint64_t; 86 | #endif 87 | 88 | ////////////////////////////////////////////////////////////////////////// 89 | 90 | /// ranking factors interface, v1 91 | /// functions that unpack PACKEDFACTORS() blob into a few helper C structures 92 | /// slower because of malloc()s and copying, but easier to use 93 | 94 | /// unpacked representation of all the field-level ranking factors 95 | typedef struct st_sphinx_field_factors 96 | { 97 | unsigned int hit_count; 98 | unsigned int id; 99 | unsigned int lcs; 100 | unsigned int word_count; 101 | float tf_idf; 102 | float min_idf; 103 | float max_idf; 104 | float sum_idf; 105 | int min_hit_pos; 106 | int min_best_span_pos; 107 | char exact_hit; 108 | int max_window_hits; 109 | int min_gaps; ///< added in v.3 110 | char exact_order; ///< added in v.4 111 | float atc; ///< added in v.4 112 | int lccs; ///< added in v.5 113 | float wlccs; ///< added in v.5 114 | char exact_field_hit; ///< added in v.10 115 | char full_field_hit; ///< added in v.11 116 | } SPH_UDF_FIELD_FACTORS; 117 | 118 | /// unpacked representation of all the term-level ranking factors 119 | typedef struct st_sphinx_term_factors 120 | { 121 | unsigned int keyword_mask; 122 | unsigned int id; 123 | int tf; 124 | float idf; 125 | } SPH_UDF_TERM_FACTORS; 126 | 127 | /// unpacked representation of all the ranking factors (document, field, and term-level) 128 | typedef struct st_sphinx_factors 129 | { 130 | int doc_bm15; 131 | float doc_bm25a; 132 | unsigned int matched_fields; 133 | int doc_word_count; 134 | int num_fields; 135 | int max_uniq_qpos; 136 | 137 | SPH_UDF_FIELD_FACTORS * field; 138 | SPH_UDF_TERM_FACTORS * term; 139 | int * field_tf; 140 | } SPH_UDF_FACTORS; 141 | 142 | /// helper function that must be called to initialize the SPH_UDF_FACTORS structure 143 | /// before it is passed to sphinx_factors_unpack 144 | /// returns 0 on success 145 | /// returns an error code on error 146 | int sphinx_factors_init ( SPH_UDF_FACTORS * out ); 147 | 148 | /// helper function that unpacks PACKEDFACTORS() blob into SPH_UDF_FACTORS structure 149 | /// MUST be in sync with PackFactors() method in sphinxsearch.cpp 150 | /// returns 0 on success 151 | /// returns an error code on error 152 | int sphinx_factors_unpack ( const unsigned int * in, SPH_UDF_FACTORS * out ); 153 | 154 | /// helper function that must be called to free the memory allocated by the sphinx_factors_unpack 155 | /// function call 156 | /// returns 0 on success 157 | /// returns an error code on error 158 | int sphinx_factors_deinit ( SPH_UDF_FACTORS * out ); 159 | 160 | ////////////////////////////////////////////////////////////////////////// 161 | 162 | /// ranking factors interface, v2 163 | /// functions that access factor values directly in the PACKEDFACTORS() blob 164 | /// 165 | /// faster, as no memory allocations are guaranteed, but type-punned 166 | /// meaning that you have to call a proper get_xxx_factor_int() or xxx_float() variant 167 | /// the accessor functions themselves will NOT perform any type checking or conversions 168 | /// or in other words, sphinx_get_field_factor_int() on a float factor like min_idf is legal, 169 | /// but returns "garbage" (floating value from the blob reinterpreted as an integer) 170 | 171 | enum sphinx_doc_factor 172 | { 173 | SPH_DOCF_BM15 = 1, ///< int 174 | SPH_DOCF_BM25A = 2, ///< float 175 | SPH_DOCF_MATCHED_FIELDS = 3, ///< unsigned int 176 | SPH_DOCF_DOC_WORD_COUNT = 4, ///< int 177 | SPH_DOCF_NUM_FIELDS = 5, ///< int 178 | SPH_DOCF_MAX_UNIQ_QPOS = 6, ///< int 179 | SPH_DOCF_EXACT_HIT_MASK = 7, ///< unsigned int 180 | SPH_DOCF_EXACT_ORDER_MASK = 8, ///< v.4, unsigned int 181 | SPH_DOCF_EXACT_FIELD_HIT_MASK = 9, ///< v.10, unsigned int 182 | SPH_DOCF_FULL_FIELD_HIT_MASK = 10 ///< v.11, unsigned int 183 | }; 184 | 185 | enum sphinx_field_factor 186 | { 187 | SPH_FIELDF_HIT_COUNT = 1, ///< unsigned int 188 | SPH_FIELDF_LCS = 2, ///< unsigned int 189 | SPH_FIELDF_WORD_COUNT = 3, ///< unsigned int 190 | SPH_FIELDF_TF_IDF = 4, ///< float 191 | SPH_FIELDF_MIN_IDF = 5, ///< float 192 | SPH_FIELDF_MAX_IDF = 6, ///< float 193 | SPH_FIELDF_SUM_IDF = 7, ///< float 194 | SPH_FIELDF_MIN_HIT_POS = 8, ///< int 195 | SPH_FIELDF_MIN_BEST_SPAN_POS = 9, ///< int 196 | SPH_FIELDF_MAX_WINDOW_HITS = 10, ///< int 197 | SPH_FIELDF_MIN_GAPS = 11, ///< v.3, int 198 | SPH_FIELDF_ATC = 12, ///< v.4, float 199 | SPH_FIELDF_LCCS = 13, ///< v.5, int 200 | SPH_FIELDF_WLCCS = 14 ///< v.5, float 201 | }; 202 | 203 | enum sphinx_term_factor 204 | { 205 | SPH_TERMF_KEYWORD_MASK = 1, ///< unsigned int 206 | SPH_TERMF_TF = 2, ///< int 207 | SPH_TERMF_IDF = 3 ///< float 208 | }; 209 | 210 | /// returns a pointer to the field factors, or NULL for a non-matched field index 211 | const unsigned int * sphinx_get_field_factors ( const unsigned int * in, int field ); 212 | 213 | /// returns a pointer to the term factors, or NULL for a non-matched field index 214 | const unsigned int * sphinx_get_term_factors ( const unsigned int * in, int term ); 215 | 216 | /// returns a document factor value, interpreted as integer 217 | int sphinx_get_doc_factor_int ( const unsigned int * in, enum sphinx_doc_factor f ); 218 | 219 | /// returns a document factor value, interpreted as float 220 | float sphinx_get_doc_factor_float ( const unsigned int * in, enum sphinx_doc_factor f ); 221 | 222 | /// returns a field factor value, interpreted as integer 223 | int sphinx_get_field_factor_int ( const unsigned int * in, enum sphinx_field_factor f ); 224 | 225 | /// returns a field factor value, interpreted as float 226 | float sphinx_get_field_factor_float ( const unsigned int * in, enum sphinx_field_factor f ); 227 | 228 | /// returns a term factor value, interpreted as integer 229 | int sphinx_get_term_factor_int ( const unsigned int * in, enum sphinx_term_factor f ); 230 | 231 | /// returns a term factor value, interpreted as float 232 | float sphinx_get_term_factor_float ( const unsigned int * in, enum sphinx_term_factor f ); 233 | 234 | /// returns a pointer to document factor value, interpreted as vector of integers 235 | const unsigned int * sphinx_get_doc_factor_ptr ( const unsigned int * in, enum sphinx_doc_factor f ); 236 | 237 | ////////////////////////////////////////////////////////////////////////// 238 | // RANKER PLUGINS 239 | ////////////////////////////////////////////////////////////////////////// 240 | 241 | /// ranker plugin intialization info 242 | typedef struct st_plugin_rankerinfo 243 | { 244 | int num_field_weights; 245 | int * field_weights; 246 | const char * options; 247 | unsigned int payload_mask; 248 | int num_query_words; 249 | int max_qpos; 250 | } SPH_RANKER_INIT; 251 | 252 | /// a structure that represents a hit 253 | typedef struct st_plugin_hit 254 | { 255 | sphinx_uint64_t doc_id; 256 | unsigned int hit_pos; 257 | unsigned short query_pos; 258 | unsigned short node_pos; 259 | unsigned short span_length; 260 | unsigned short match_length; 261 | unsigned int weight; 262 | unsigned int query_pos_mask; 263 | } SPH_RANKER_HIT; 264 | 265 | #ifdef __cplusplus 266 | } 267 | #endif 268 | 269 | #endif // _sphinxudf_ 270 | 271 | // 272 | // $Id$ 273 | // 274 | -------------------------------------------------------------------------------- /sphinx/src/udfexample.c: -------------------------------------------------------------------------------- 1 | // 2 | // $Id$ 3 | // 4 | 5 | // 6 | // Sphinx UDF function example 7 | // 8 | // Linux 9 | // gcc -fPIC -shared -o udfexample.so udfexample.c 10 | // CREATE FUNCTION sequence RETURNS INT SONAME 'udfexample.so'; 11 | // CREATE FUNCTION strtoint RETURNS INT SONAME 'udfexample.so'; 12 | // CREATE FUNCTION avgmva RETURNS FLOAT SONAME 'udfexample.so'; 13 | // CREATE FUNCTION failtest RETURNS STRING SONAME 'udfexample.so'; 14 | // 15 | // Windows 16 | // cl /MTd /LD udfexample.c 17 | // CREATE FUNCTION sequence RETURNS INT SONAME 'udfexample.dll'; 18 | // CREATE FUNCTION strtoint RETURNS INT SONAME 'udfexample.dll'; 19 | // CREATE FUNCTION avgmva RETURNS FLOAT SONAME 'udfexample.dll'; 20 | // CREATE FUNCTION failtest RETURNS STRING SONAME 'udfexample.dll'; 21 | // 22 | 23 | #include "sphinxudf.h" 24 | #include <stdio.h> 25 | #include <string.h> 26 | #include <stdlib.h> 27 | 28 | #ifdef _MSC_VER 29 | #define snprintf _snprintf 30 | #define DLLEXPORT __declspec(dllexport) 31 | #else 32 | #define DLLEXPORT 33 | #endif 34 | 35 | /// UDF version control 36 | /// gets called once when the library is loaded 37 | DLLEXPORT int udfexample_ver () 38 | { 39 | return SPH_UDF_VERSION; 40 | } 41 | 42 | 43 | /// UDF re-initialization func 44 | /// gets called on sighup (workers=prefork only) 45 | DLLEXPORT void udfexample_reinit () 46 | { 47 | } 48 | 49 | 50 | /// UDF initialization 51 | /// gets called on every query, when query begins 52 | /// args are filled with values for a particular query 53 | DLLEXPORT int sequence_init ( SPH_UDF_INIT * init, SPH_UDF_ARGS * args, char * error_message ) 54 | { 55 | // check argument count 56 | if ( args->arg_count > 1 ) 57 | { 58 | snprintf ( error_message, SPH_UDF_ERROR_LEN, "SEQUENCE() takes either 0 or 1 arguments" ); 59 | return 1; 60 | } 61 | 62 | // check argument type 63 | if ( args->arg_count && args->arg_types[0]!=SPH_UDF_TYPE_UINT32 ) 64 | { 65 | snprintf ( error_message, SPH_UDF_ERROR_LEN, "SEQUENCE() requires 1st argument to be uint" ); 66 | return 1; 67 | } 68 | 69 | // allocate and init counter storage 70 | init->func_data = (void*) malloc ( sizeof(int) ); 71 | if ( !init->func_data ) 72 | { 73 | snprintf ( error_message, SPH_UDF_ERROR_LEN, "malloc() failed" ); 74 | return 1; 75 | } 76 | *(int*)init->func_data = 1; 77 | 78 | // all done 79 | return 0; 80 | } 81 | 82 | 83 | /// UDF deinitialization 84 | /// gets called on every query, when query ends 85 | DLLEXPORT void sequence_deinit ( SPH_UDF_INIT * init ) 86 | { 87 | // deallocate storage 88 | if ( init->func_data ) 89 | { 90 | free ( init->func_data ); 91 | init->func_data = NULL; 92 | } 93 | } 94 | 95 | 96 | /// UDF implementation 97 | /// gets called for every row, unless optimized away 98 | DLLEXPORT sphinx_int64_t sequence ( SPH_UDF_INIT * init, SPH_UDF_ARGS * args, char * error_message ) 99 | { 100 | int res = (*(int*)init->func_data)++; 101 | if ( args->arg_count ) 102 | res += *(int*)args->arg_values[0]; 103 | return res; 104 | } 105 | 106 | ////////////////////////////////////////////////////////////////////////// 107 | 108 | DLLEXPORT int strtoint_init ( SPH_UDF_INIT * init, SPH_UDF_ARGS * args, char * error_message ) 109 | { 110 | if ( args->arg_count!=1 || args->arg_types[0]!=SPH_UDF_TYPE_STRING ) 111 | { 112 | snprintf ( error_message, SPH_UDF_ERROR_LEN, "STRTOINT() requires 1 string argument" ); 113 | return 1; 114 | } 115 | return 0; 116 | } 117 | 118 | DLLEXPORT sphinx_int64_t strtoint ( SPH_UDF_INIT * init, SPH_UDF_ARGS * args, char * error_message ) 119 | { 120 | const char * s = args->arg_values[0]; 121 | int len = args->str_lengths[0], res = 0; 122 | 123 | while ( len>0 && *s>='0' && *s<='9' ) 124 | { 125 | res += *s - '0'; 126 | len--; 127 | } 128 | 129 | return res; 130 | } 131 | 132 | ////////////////////////////////////////////////////////////////////////// 133 | 134 | DLLEXPORT int avgmva_init ( SPH_UDF_INIT * init, SPH_UDF_ARGS * args, char * error_message ) 135 | { 136 | if ( args->arg_count!=1 || 137 | ( args->arg_types[0]!=SPH_UDF_TYPE_UINT32SET && args->arg_types[0]!=SPH_UDF_TYPE_INT64SET ) ) 138 | { 139 | snprintf ( error_message, SPH_UDF_ERROR_LEN, "AVGMVA() requires 1 MVA argument" ); 140 | return 1; 141 | } 142 | 143 | // store our mva vs mva64 flag to func_data 144 | init->func_data = (void*)(int)( args->arg_types[0]==SPH_UDF_TYPE_INT64SET ? 1 : 0 ); 145 | return 0; 146 | } 147 | 148 | DLLEXPORT double avgmva ( SPH_UDF_INIT * init, SPH_UDF_ARGS * args, char * error_message ) 149 | { 150 | unsigned int * mva = (unsigned int *) args->arg_values[0]; 151 | double res = 0; 152 | int i, n, is64; 153 | 154 | if ( !mva ) 155 | return res; 156 | 157 | // Both MVA32 and MVA64 are stored as dword (unsigned 32-bit) arrays. 158 | // The first dword stores the array length (always in dwords too), and 159 | // the next ones store the values. In pseudocode: 160 | // 161 | // unsigned int num_dwords 162 | // unsigned int data [ num_dwords ] 163 | // 164 | // With MVA32, this lets you access the values pretty naturally. 165 | // 166 | // With MVA64, however, we have to do a few tricks: 167 | // a) divide num_dwords by 2 to get the number of 64-bit elements, 168 | // b) assemble those 64-bit values from dword pairs. 169 | // 170 | // The latter is required for architectures where non-aligned 171 | // 64-bit access crashes. On Intel, we could have also done it 172 | // like this: 173 | // 174 | // int * raw_ptr = (int*) args->arg_values[0]; 175 | // int mva64_count = (*raw_ptr) / 2; 176 | // sphinx_uint64_t * mva64_values = (sphinx_uint64_t*)(raw_ptr + 1); 177 | 178 | // pull "mva32 or mva64" flag (that we stored in _init) from func_data 179 | is64 = (int)(init->func_data) != 0; 180 | if ( is64 ) 181 | { 182 | // handle mva64 183 | n = *mva++ / 2; 184 | for ( i=0; i<n; i++ ) 185 | { 186 | res += (((sphinx_uint64_t)mva[1]) << 32) + mva[0]; 187 | mva += 2; 188 | } 189 | } else 190 | { 191 | // handle mva32 192 | n = *mva++; 193 | for ( i=0; i<n; i++ ) 194 | res += *mva++; 195 | } 196 | 197 | return res/n; 198 | } 199 | 200 | ////////////////////////////////////////////////////////////////////////// 201 | 202 | DLLEXPORT int failtest_init ( SPH_UDF_INIT * init, SPH_UDF_ARGS * args, char * error_message ) 203 | { 204 | if ( args->arg_count != 0 ) 205 | { 206 | snprintf ( error_message, SPH_UDF_ERROR_LEN, "FAILTEST() takes 0 arguments" ); 207 | return 1; 208 | } 209 | 210 | // allocate counter storage, init counter 211 | init->func_data = (void*) malloc ( sizeof(int) ); 212 | if ( !init->func_data ) 213 | { 214 | snprintf ( error_message, SPH_UDF_ERROR_LEN, "malloc() failed" ); 215 | return 1; 216 | } 217 | *(int*)init->func_data = 1; 218 | 219 | return 0; 220 | } 221 | 222 | DLLEXPORT char * failtest ( SPH_UDF_INIT * init, SPH_UDF_ARGS * args, char * error_message ) 223 | { 224 | int a = (*(int*)init->func_data)++; 225 | 226 | if ( a < 4 ) 227 | { 228 | // IMPORTANT! 229 | // note that strings we return to Sphinx MUST be allocated using fn_malloc! 230 | char * res = args->fn_malloc ( 16 ); 231 | snprintf ( res, 16, "val%d", a ); 232 | return res; 233 | } 234 | 235 | // starting with UDF version 12, we can emit an error message from the main 236 | // UDF function too, and not just a 1-byte flag 237 | snprintf ( error_message, SPH_UDF_ERROR_LEN, "err%d", a ); 238 | return NULL; 239 | } 240 | 241 | DLLEXPORT void failtest_deinit ( SPH_UDF_INIT * init ) 242 | { 243 | // deallocate storage 244 | if ( init->func_data ) 245 | { 246 | free ( init->func_data ); 247 | init->func_data = NULL; 248 | } 249 | } 250 | 251 | // FIXME! add a ranker plugin example? 252 | 253 | // 254 | // $Id$ 255 | // 256 | -------------------------------------------------------------------------------- /sphinxapi.php: -------------------------------------------------------------------------------- 1 | <?php 2 | 3 | // 4 | // $Id$ 5 | // 6 | 7 | // 8 | // Copyright (c) 2001-2010, Andrew Aksyonoff 9 | // Copyright (c) 2008-2010, Sphinx Technologies Inc 10 | // All rights reserved 11 | // 12 | // This program is free software; you can redistribute it and/or modify 13 | // it under the terms of the GNU General Public License. You should have 14 | // received a copy of the GPL license along with this program; if you 15 | // did not, you can find it at http://www.gnu.org/ 16 | // 17 | 18 | ///////////////////////////////////////////////////////////////////////////// 19 | // PHP version of Sphinx searchd client (PHP API) 20 | ///////////////////////////////////////////////////////////////////////////// 21 | 22 | /// known searchd commands 23 | define ( "SEARCHD_COMMAND_SEARCH", 0 ); 24 | define ( "SEARCHD_COMMAND_EXCERPT", 1 ); 25 | define ( "SEARCHD_COMMAND_UPDATE", 2 ); 26 | define ( "SEARCHD_COMMAND_KEYWORDS", 3 ); 27 | define ( "SEARCHD_COMMAND_PERSIST", 4 ); 28 | define ( "SEARCHD_COMMAND_STATUS", 5 ); 29 | define ( "SEARCHD_COMMAND_QUERY", 6 ); 30 | define ( "SEARCHD_COMMAND_FLUSHATTRS", 7 ); 31 | 32 | /// current client-side command implementation versions 33 | define ( "VER_COMMAND_SEARCH", 0x117 ); 34 | define ( "VER_COMMAND_EXCERPT", 0x103 ); 35 | define ( "VER_COMMAND_UPDATE", 0x102 ); 36 | define ( "VER_COMMAND_KEYWORDS", 0x100 ); 37 | define ( "VER_COMMAND_STATUS", 0x100 ); 38 | define ( "VER_COMMAND_QUERY", 0x100 ); 39 | define ( "VER_COMMAND_FLUSHATTRS", 0x100 ); 40 | 41 | /// known searchd status codes 42 | define ( "SEARCHD_OK", 0 ); 43 | define ( "SEARCHD_ERROR", 1 ); 44 | define ( "SEARCHD_RETRY", 2 ); 45 | define ( "SEARCHD_WARNING", 3 ); 46 | 47 | /// known match modes 48 | define ( "SPH_MATCH_ALL", 0 ); 49 | define ( "SPH_MATCH_ANY", 1 ); 50 | define ( "SPH_MATCH_PHRASE", 2 ); 51 | define ( "SPH_MATCH_BOOLEAN", 3 ); 52 | define ( "SPH_MATCH_EXTENDED", 4 ); 53 | define ( "SPH_MATCH_FULLSCAN", 5 ); 54 | define ( "SPH_MATCH_EXTENDED2", 6 ); // extended engine V2 (TEMPORARY, WILL BE REMOVED) 55 | 56 | /// known ranking modes (ext2 only) 57 | define ( "SPH_RANK_PROXIMITY_BM25", 0 ); ///< default mode, phrase proximity major factor and BM25 minor one 58 | define ( "SPH_RANK_BM25", 1 ); ///< statistical mode, BM25 ranking only (faster but worse quality) 59 | define ( "SPH_RANK_NONE", 2 ); ///< no ranking, all matches get a weight of 1 60 | define ( "SPH_RANK_WORDCOUNT", 3 ); ///< simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts 61 | define ( "SPH_RANK_PROXIMITY", 4 ); 62 | define ( "SPH_RANK_MATCHANY", 5 ); 63 | define ( "SPH_RANK_FIELDMASK", 6 ); 64 | define ( "SPH_RANK_SPH04", 7 ); 65 | define ( "SPH_RANK_TOTAL", 8 ); 66 | 67 | /// known sort modes 68 | define ( "SPH_SORT_RELEVANCE", 0 ); 69 | define ( "SPH_SORT_ATTR_DESC", 1 ); 70 | define ( "SPH_SORT_ATTR_ASC", 2 ); 71 | define ( "SPH_SORT_TIME_SEGMENTS", 3 ); 72 | define ( "SPH_SORT_EXTENDED", 4 ); 73 | define ( "SPH_SORT_EXPR", 5 ); 74 | 75 | /// known filter types 76 | define ( "SPH_FILTER_VALUES", 0 ); 77 | define ( "SPH_FILTER_RANGE", 1 ); 78 | define ( "SPH_FILTER_FLOATRANGE", 2 ); 79 | 80 | /// known attribute types 81 | define ( "SPH_ATTR_INTEGER", 1 ); 82 | define ( "SPH_ATTR_TIMESTAMP", 2 ); 83 | define ( "SPH_ATTR_ORDINAL", 3 ); 84 | define ( "SPH_ATTR_BOOL", 4 ); 85 | define ( "SPH_ATTR_FLOAT", 5 ); 86 | define ( "SPH_ATTR_BIGINT", 6 ); 87 | define ( "SPH_ATTR_STRING", 7 ); 88 | define ( "SPH_ATTR_MULTI", 0x40000000 ); 89 | 90 | /// known grouping functions 91 | define ( "SPH_GROUPBY_DAY", 0 ); 92 | define ( "SPH_GROUPBY_WEEK", 1 ); 93 | define ( "SPH_GROUPBY_MONTH", 2 ); 94 | define ( "SPH_GROUPBY_YEAR", 3 ); 95 | define ( "SPH_GROUPBY_ATTR", 4 ); 96 | define ( "SPH_GROUPBY_ATTRPAIR", 5 ); 97 | 98 | // important properties of PHP's integers: 99 | // - always signed (one bit short of PHP_INT_SIZE) 100 | // - conversion from string to int is saturated 101 | // - float is double 102 | // - div converts arguments to floats 103 | // - mod converts arguments to ints 104 | 105 | // the packing code below works as follows: 106 | // - when we got an int, just pack it 107 | // if performance is a problem, this is the branch users should aim for 108 | // 109 | // - otherwise, we got a number in string form 110 | // this might be due to different reasons, but we assume that this is 111 | // because it didn't fit into PHP int 112 | // 113 | // - factor the string into high and low ints for packing 114 | // - if we have bcmath, then it is used 115 | // - if we don't, we have to do it manually (this is the fun part) 116 | // 117 | // - x64 branch does factoring using ints 118 | // - x32 (ab)uses floats, since we can't fit unsigned 32-bit number into an int 119 | // 120 | // unpacking routines are pretty much the same. 121 | // - return ints if we can 122 | // - otherwise format number into a string 123 | 124 | /// pack 64-bit signed 125 | function sphPackI64 ( $v ) 126 | { 127 | assert ( is_numeric($v) ); 128 | 129 | // x64 130 | if ( PHP_INT_SIZE>=8 ) 131 | { 132 | $v = (int)$v; 133 | return pack ( "NN", $v>>32, $v&0xFFFFFFFF ); 134 | } 135 | 136 | // x32, int 137 | if ( is_int($v) ) 138 | return pack ( "NN", $v < 0 ? -1 : 0, $v ); 139 | 140 | // x32, bcmath 141 | if ( function_exists("bcmul") ) 142 | { 143 | if ( bccomp ( $v, 0 ) == -1 ) 144 | $v = bcadd ( "18446744073709551616", $v ); 145 | $h = bcdiv ( $v, "4294967296", 0 ); 146 | $l = bcmod ( $v, "4294967296" ); 147 | return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit 148 | } 149 | 150 | // x32, no-bcmath 151 | $p = max(0, strlen($v) - 13); 152 | $lo = abs((float)substr($v, $p)); 153 | $hi = abs((float)substr($v, 0, $p)); 154 | 155 | $m = $lo + $hi*1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912 156 | $q = floor($m/4294967296.0); 157 | $l = $m - ($q*4294967296.0); 158 | $h = $hi*2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328 159 | 160 | if ( $v<0 ) 161 | { 162 | if ( $l==0 ) 163 | $h = 4294967296.0 - $h; 164 | else 165 | { 166 | $h = 4294967295.0 - $h; 167 | $l = 4294967296.0 - $l; 168 | } 169 | } 170 | return pack ( "NN", $h, $l ); 171 | } 172 | 173 | /// pack 64-bit unsigned 174 | function sphPackU64 ( $v ) 175 | { 176 | assert ( is_numeric($v) ); 177 | 178 | // x64 179 | if ( PHP_INT_SIZE>=8 ) 180 | { 181 | assert ( $v>=0 ); 182 | 183 | // x64, int 184 | if ( is_int($v) ) 185 | return pack ( "NN", $v>>32, $v&0xFFFFFFFF ); 186 | 187 | // x64, bcmath 188 | if ( function_exists("bcmul") ) 189 | { 190 | $h = bcdiv ( $v, 4294967296, 0 ); 191 | $l = bcmod ( $v, 4294967296 ); 192 | return pack ( "NN", $h, $l ); 193 | } 194 | 195 | // x64, no-bcmath 196 | $p = max ( 0, strlen($v) - 13 ); 197 | $lo = (int)substr ( $v, $p ); 198 | $hi = (int)substr ( $v, 0, $p ); 199 | 200 | $m = $lo + $hi*1316134912; 201 | $l = $m % 4294967296; 202 | $h = $hi*2328 + (int)($m/4294967296); 203 | 204 | return pack ( "NN", $h, $l ); 205 | } 206 | 207 | // x32, int 208 | if ( is_int($v) ) 209 | return pack ( "NN", 0, $v ); 210 | 211 | // x32, bcmath 212 | if ( function_exists("bcmul") ) 213 | { 214 | $h = bcdiv ( $v, "4294967296", 0 ); 215 | $l = bcmod ( $v, "4294967296" ); 216 | return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit 217 | } 218 | 219 | // x32, no-bcmath 220 | $p = max(0, strlen($v) - 13); 221 | $lo = (float)substr($v, $p); 222 | $hi = (float)substr($v, 0, $p); 223 | 224 | $m = $lo + $hi*1316134912.0; 225 | $q = floor($m / 4294967296.0); 226 | $l = $m - ($q * 4294967296.0); 227 | $h = $hi*2328.0 + $q; 228 | 229 | return pack ( "NN", $h, $l ); 230 | } 231 | 232 | // unpack 64-bit unsigned 233 | function sphUnpackU64 ( $v ) 234 | { 235 | list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) ); 236 | 237 | if ( PHP_INT_SIZE>=8 ) 238 | { 239 | if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again 240 | if ( $lo<0 ) $lo += (1<<32); 241 | 242 | // x64, int 243 | if ( $hi<=2147483647 ) 244 | return ($hi<<32) + $lo; 245 | 246 | // x64, bcmath 247 | if ( function_exists("bcmul") ) 248 | return bcadd ( $lo, bcmul ( $hi, "4294967296" ) ); 249 | 250 | // x64, no-bcmath 251 | $C = 100000; 252 | $h = ((int)($hi / $C) << 32) + (int)($lo / $C); 253 | $l = (($hi % $C) << 32) + ($lo % $C); 254 | if ( $l>$C ) 255 | { 256 | $h += (int)($l / $C); 257 | $l = $l % $C; 258 | } 259 | 260 | if ( $h==0 ) 261 | return $l; 262 | return sprintf ( "%d%05d", $h, $l ); 263 | } 264 | 265 | // x32, int 266 | if ( $hi==0 ) 267 | { 268 | if ( $lo>0 ) 269 | return $lo; 270 | return sprintf ( "%u", $lo ); 271 | } 272 | 273 | $hi = sprintf ( "%u", $hi ); 274 | $lo = sprintf ( "%u", $lo ); 275 | 276 | // x32, bcmath 277 | if ( function_exists("bcmul") ) 278 | return bcadd ( $lo, bcmul ( $hi, "4294967296" ) ); 279 | 280 | // x32, no-bcmath 281 | $hi = (float)$hi; 282 | $lo = (float)$lo; 283 | 284 | $q = floor($hi/10000000.0); 285 | $r = $hi - $q*10000000.0; 286 | $m = $lo + $r*4967296.0; 287 | $mq = floor($m/10000000.0); 288 | $l = $m - $mq*10000000.0; 289 | $h = $q*4294967296.0 + $r*429.0 + $mq; 290 | 291 | $h = sprintf ( "%.0f", $h ); 292 | $l = sprintf ( "%07.0f", $l ); 293 | if ( $h=="0" ) 294 | return sprintf( "%.0f", (float)$l ); 295 | return $h . $l; 296 | } 297 | 298 | // unpack 64-bit signed 299 | function sphUnpackI64 ( $v ) 300 | { 301 | list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) ); 302 | 303 | // x64 304 | if ( PHP_INT_SIZE>=8 ) 305 | { 306 | if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again 307 | if ( $lo<0 ) $lo += (1<<32); 308 | 309 | return ($hi<<32) + $lo; 310 | } 311 | 312 | // x32, int 313 | if ( $hi==0 ) 314 | { 315 | if ( $lo>0 ) 316 | return $lo; 317 | return sprintf ( "%u", $lo ); 318 | } 319 | // x32, int 320 | elseif ( $hi==-1 ) 321 | { 322 | if ( $lo<0 ) 323 | return $lo; 324 | return sprintf ( "%.0f", $lo - 4294967296.0 ); 325 | } 326 | 327 | $neg = ""; 328 | $c = 0; 329 | if ( $hi<0 ) 330 | { 331 | $hi = ~$hi; 332 | $lo = ~$lo; 333 | $c = 1; 334 | $neg = "-"; 335 | } 336 | 337 | $hi = sprintf ( "%u", $hi ); 338 | $lo = sprintf ( "%u", $lo ); 339 | 340 | // x32, bcmath 341 | if ( function_exists("bcmul") ) 342 | return $neg . bcadd ( bcadd ( $lo, bcmul ( $hi, "4294967296" ) ), $c ); 343 | 344 | // x32, no-bcmath 345 | $hi = (float)$hi; 346 | $lo = (float)$lo; 347 | 348 | $q = floor($hi/10000000.0); 349 | $r = $hi - $q*10000000.0; 350 | $m = $lo + $r*4967296.0; 351 | $mq = floor($m/10000000.0); 352 | $l = $m - $mq*10000000.0 + $c; 353 | $h = $q*4294967296.0 + $r*429.0 + $mq; 354 | if ( $l==10000000 ) 355 | { 356 | $l = 0; 357 | $h += 1; 358 | } 359 | 360 | $h = sprintf ( "%.0f", $h ); 361 | $l = sprintf ( "%07.0f", $l ); 362 | if ( $h=="0" ) 363 | return $neg . sprintf( "%.0f", (float)$l ); 364 | return $neg . $h . $l; 365 | } 366 | 367 | 368 | function sphFixUint ( $value ) 369 | { 370 | if ( PHP_INT_SIZE>=8 ) 371 | { 372 | // x64 route, workaround broken unpack() in 5.2.2+ 373 | if ( $value<0 ) $value += (1<<32); 374 | return $value; 375 | } 376 | else 377 | { 378 | // x32 route, workaround php signed/unsigned braindamage 379 | return sprintf ( "%u", $value ); 380 | } 381 | } 382 | 383 | 384 | /// sphinx searchd client class 385 | class SphinxClient 386 | { 387 | var $_host; ///< searchd host (default is "localhost") 388 | var $_port; ///< searchd port (default is 9312) 389 | var $_offset; ///< how many records to seek from result-set start (default is 0) 390 | var $_limit; ///< how many records to return from result-set starting at offset (default is 20) 391 | var $_mode; ///< query matching mode (default is SPH_MATCH_ALL) 392 | var $_weights; ///< per-field weights (default is 1 for all fields) 393 | var $_sort; ///< match sorting mode (default is SPH_SORT_RELEVANCE) 394 | var $_sortby; ///< attribute to sort by (defualt is "") 395 | var $_min_id; ///< min ID to match (default is 0, which means no limit) 396 | var $_max_id; ///< max ID to match (default is 0, which means no limit) 397 | var $_filters; ///< search filters 398 | var $_groupby; ///< group-by attribute name 399 | var $_groupfunc; ///< group-by function (to pre-process group-by attribute value with) 400 | var $_groupsort; ///< group-by sorting clause (to sort groups in result set with) 401 | var $_groupdistinct;///< group-by count-distinct attribute 402 | var $_maxmatches; ///< max matches to retrieve 403 | var $_cutoff; ///< cutoff to stop searching at (default is 0) 404 | var $_retrycount; ///< distributed retries count 405 | var $_retrydelay; ///< distributed retries delay 406 | var $_anchor; ///< geographical anchor point 407 | var $_indexweights; ///< per-index weights 408 | var $_ranker; ///< ranking mode (default is SPH_RANK_PROXIMITY_BM25) 409 | var $_maxquerytime; ///< max query time, milliseconds (default is 0, do not limit) 410 | var $_fieldweights; ///< per-field-name weights 411 | var $_overrides; ///< per-query attribute values overrides 412 | var $_select; ///< select-list (attributes or expressions, with optional aliases) 413 | 414 | var $_error; ///< last error message 415 | var $_warning; ///< last warning message 416 | var $_connerror; ///< connection error vs remote error flag 417 | 418 | var $_reqs; ///< requests array for multi-query 419 | var $_mbenc; ///< stored mbstring encoding 420 | var $_arrayresult; ///< whether $result["matches"] should be a hash or an array 421 | var $_timeout; ///< connect timeout 422 | 423 | ///////////////////////////////////////////////////////////////////////////// 424 | // common stuff 425 | ///////////////////////////////////////////////////////////////////////////// 426 | 427 | /// create a new client object and fill defaults 428 | function SphinxClient () 429 | { 430 | // per-client-object settings 431 | $this->_host = "localhost"; 432 | $this->_port = 9321; 433 | $this->_path = false; 434 | $this->_socket = false; 435 | 436 | // per-query settings 437 | $this->_offset = 0; 438 | $this->_limit = 20; 439 | $this->_mode = SPH_MATCH_ALL; 440 | $this->_weights = array (); 441 | $this->_sort = SPH_SORT_RELEVANCE; 442 | $this->_sortby = ""; 443 | $this->_min_id = 0; 444 | $this->_max_id = 0; 445 | $this->_filters = array (); 446 | $this->_groupby = ""; 447 | $this->_groupfunc = SPH_GROUPBY_DAY; 448 | $this->_groupsort = "@group desc"; 449 | $this->_groupdistinct= ""; 450 | $this->_maxmatches = 1000; 451 | $this->_cutoff = 0; 452 | $this->_retrycount = 0; 453 | $this->_retrydelay = 0; 454 | $this->_anchor = array (); 455 | $this->_indexweights= array (); 456 | $this->_ranker = SPH_RANK_PROXIMITY_BM25; 457 | $this->_maxquerytime= 0; 458 | $this->_fieldweights= array(); 459 | $this->_overrides = array(); 460 | $this->_select = "*"; 461 | 462 | $this->_error = ""; // per-reply fields (for single-query case) 463 | $this->_warning = ""; 464 | $this->_connerror = false; 465 | 466 | $this->_reqs = array (); // requests storage (for multi-query case) 467 | $this->_mbenc = ""; 468 | $this->_arrayresult = false; 469 | $this->_timeout = 0; 470 | } 471 | 472 | function __destruct() 473 | { 474 | if ( $this->_socket !== false ) 475 | fclose ( $this->_socket ); 476 | } 477 | 478 | /// get last error message (string) 479 | function GetLastError () 480 | { 481 | return $this->_error; 482 | } 483 | 484 | /// get last warning message (string) 485 | function GetLastWarning () 486 | { 487 | return $this->_warning; 488 | } 489 | 490 | /// get last error flag (to tell network connection errors from searchd errors or broken responses) 491 | function IsConnectError() 492 | { 493 | return $this->_connerror; 494 | } 495 | 496 | /// set searchd host name (string) and port (integer) 497 | function SetServer ( $host, $port = 0 ) 498 | { 499 | assert ( is_string($host) ); 500 | if ( $host[0] == '/') 501 | { 502 | $this->_path = 'unix://' . $host; 503 | return; 504 | } 505 | if ( substr ( $host, 0, 7 )=="unix://" ) 506 | { 507 | $this->_path = $host; 508 | return; 509 | } 510 | 511 | assert ( is_int($port) ); 512 | $this->_host = $host; 513 | $this->_port = $port; 514 | $this->_path = ''; 515 | 516 | } 517 | 518 | /// set server connection timeout (0 to remove) 519 | function SetConnectTimeout ( $timeout ) 520 | { 521 | assert ( is_numeric($timeout) ); 522 | $this->_timeout = $timeout; 523 | } 524 | 525 | 526 | function _Send ( $handle, $data, $length ) 527 | { 528 | if ( feof($handle) || fwrite ( $handle, $data, $length ) !== $length ) 529 | { 530 | $this->_error = 'connection unexpectedly closed (timed out?)'; 531 | $this->_connerror = true; 532 | return false; 533 | } 534 | return true; 535 | } 536 | 537 | ///////////////////////////////////////////////////////////////////////////// 538 | 539 | /// enter mbstring workaround mode 540 | function _MBPush () 541 | { 542 | $this->_mbenc = ""; 543 | if ( ini_get ( "mbstring.func_overload" ) & 2 ) 544 | { 545 | $this->_mbenc = mb_internal_encoding(); 546 | mb_internal_encoding ( "latin1" ); 547 | } 548 | } 549 | 550 | /// leave mbstring workaround mode 551 | function _MBPop () 552 | { 553 | if ( $this->_mbenc ) 554 | mb_internal_encoding ( $this->_mbenc ); 555 | } 556 | 557 | /// connect to searchd server 558 | function _Connect () 559 | { 560 | if ( $this->_socket!==false ) 561 | { 562 | // we are in persistent connection mode, so we have a socket 563 | // however, need to check whether it's still alive 564 | if ( !@feof ( $this->_socket ) ) 565 | return $this->_socket; 566 | 567 | // force reopen 568 | $this->_socket = false; 569 | } 570 | 571 | $errno = 0; 572 | $errstr = ""; 573 | $this->_connerror = false; 574 | 575 | if ( $this->_path ) 576 | { 577 | $host = $this->_path; 578 | $port = 0; 579 | } 580 | else 581 | { 582 | $host = $this->_host; 583 | $port = $this->_port; 584 | } 585 | 586 | if ( $this->_timeout<=0 ) 587 | $fp = @fsockopen ( $host, $port, $errno, $errstr ); 588 | else 589 | $fp = @fsockopen ( $host, $port, $errno, $errstr, $this->_timeout ); 590 | 591 | if ( !$fp ) 592 | { 593 | if ( $this->_path ) 594 | $location = $this->_path; 595 | else 596 | $location = "{$this->_host}:{$this->_port}"; 597 | 598 | $errstr = trim ( $errstr ); 599 | $this->_error = "connection to $location failed (errno=$errno, msg=$errstr)"; 600 | $this->_connerror = true; 601 | return false; 602 | } 603 | 604 | // send my version 605 | // this is a subtle part. we must do it before (!) reading back from searchd. 606 | // because otherwise under some conditions (reported on FreeBSD for instance) 607 | // TCP stack could throttle write-write-read pattern because of Nagle. 608 | if ( !$this->_Send ( $fp, pack ( "N", 1 ), 4 ) ) 609 | { 610 | fclose ( $fp ); 611 | $this->_error = "failed to send client protocol version"; 612 | return false; 613 | } 614 | 615 | // check version 616 | list(,$v) = unpack ( "N*", fread ( $fp, 4 ) ); 617 | $v = (int)$v; 618 | if ( $v<1 ) 619 | { 620 | fclose ( $fp ); 621 | $this->_error = "expected searchd protocol version 1+, got version '$v'"; 622 | return false; 623 | } 624 | 625 | return $fp; 626 | } 627 | 628 | /// get and check response packet from searchd server 629 | function _GetResponse ( $fp, $client_ver ) 630 | { 631 | $response = ""; 632 | $len = 0; 633 | 634 | $header = fread ( $fp, 8 ); 635 | if ( strlen($header)==8 ) 636 | { 637 | list ( $status, $ver, $len ) = array_values ( unpack ( "n2a/Nb", $header ) ); 638 | $left = $len; 639 | while ( $left>0 && !feof($fp) ) 640 | { 641 | $chunk = fread ( $fp, $left ); 642 | if ( $chunk ) 643 | { 644 | $response .= $chunk; 645 | $left -= strlen($chunk); 646 | } 647 | } 648 | } 649 | if ( $this->_socket === false ) 650 | fclose ( $fp ); 651 | 652 | // check response 653 | $read = strlen ( $response ); 654 | if ( !$response || $read!=$len ) 655 | { 656 | $this->_error = $len 657 | ? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)" 658 | : "received zero-sized searchd response"; 659 | return false; 660 | } 661 | 662 | // check status 663 | if ( $status==SEARCHD_WARNING ) 664 | { 665 | list(,$wlen) = unpack ( "N*", substr ( $response, 0, 4 ) ); 666 | $this->_warning = substr ( $response, 4, $wlen ); 667 | return substr ( $response, 4+$wlen ); 668 | } 669 | if ( $status==SEARCHD_ERROR ) 670 | { 671 | $this->_error = "searchd error: " . substr ( $response, 4 ); 672 | return false; 673 | } 674 | if ( $status==SEARCHD_RETRY ) 675 | { 676 | $this->_error = "temporary searchd error: " . substr ( $response, 4 ); 677 | return false; 678 | } 679 | if ( $status!=SEARCHD_OK ) 680 | { 681 | $this->_error = "unknown status code '$status'"; 682 | return false; 683 | } 684 | 685 | // check version 686 | if ( $ver<$client_ver ) 687 | { 688 | $this->_warning = sprintf ( "searchd command v.%d.%d older than client's v.%d.%d, some options might not work", 689 | $ver>>8, $ver&0xff, $client_ver>>8, $client_ver&0xff ); 690 | } 691 | 692 | return $response; 693 | } 694 | 695 | ///////////////////////////////////////////////////////////////////////////// 696 | // searching 697 | ///////////////////////////////////////////////////////////////////////////// 698 | 699 | /// set offset and count into result set, 700 | /// and optionally set max-matches and cutoff limits 701 | function SetLimits ( $offset, $limit, $max=0, $cutoff=0 ) 702 | { 703 | assert ( is_int($offset) ); 704 | assert ( is_int($limit) ); 705 | assert ( $offset>=0 ); 706 | assert ( $limit>0 ); 707 | assert ( $max>=0 ); 708 | $this->_offset = $offset; 709 | $this->_limit = $limit; 710 | if ( $max>0 ) 711 | $this->_maxmatches = $max; 712 | if ( $cutoff>0 ) 713 | $this->_cutoff = $cutoff; 714 | } 715 | 716 | /// set maximum query time, in milliseconds, per-index 717 | /// integer, 0 means "do not limit" 718 | function SetMaxQueryTime ( $max ) 719 | { 720 | assert ( is_int($max) ); 721 | assert ( $max>=0 ); 722 | $this->_maxquerytime = $max; 723 | } 724 | 725 | /// set matching mode 726 | function SetMatchMode ( $mode ) 727 | { 728 | assert ( $mode==SPH_MATCH_ALL 729 | || $mode==SPH_MATCH_ANY 730 | || $mode==SPH_MATCH_PHRASE 731 | || $mode==SPH_MATCH_BOOLEAN 732 | || $mode==SPH_MATCH_EXTENDED 733 | || $mode==SPH_MATCH_FULLSCAN 734 | || $mode==SPH_MATCH_EXTENDED2 ); 735 | $this->_mode = $mode; 736 | } 737 | 738 | /// set ranking mode 739 | function SetRankingMode ( $ranker ) 740 | { 741 | assert ( $ranker>=0 && $ranker<SPH_RANK_TOTAL ); 742 | $this->_ranker = $ranker; 743 | } 744 | 745 | /// set matches sorting mode 746 | function SetSortMode ( $mode, $sortby="" ) 747 | { 748 | assert ( 749 | $mode==SPH_SORT_RELEVANCE || 750 | $mode==SPH_SORT_ATTR_DESC || 751 | $mode==SPH_SORT_ATTR_ASC || 752 | $mode==SPH_SORT_TIME_SEGMENTS || 753 | $mode==SPH_SORT_EXTENDED || 754 | $mode==SPH_SORT_EXPR ); 755 | assert ( is_string($sortby) ); 756 | assert ( $mode==SPH_SORT_RELEVANCE || strlen($sortby)>0 ); 757 | 758 | $this->_sort = $mode; 759 | $this->_sortby = $sortby; 760 | } 761 | 762 | /// bind per-field weights by order 763 | /// DEPRECATED; use SetFieldWeights() instead 764 | function SetWeights ( $weights ) 765 | { 766 | assert ( is_array($weights) ); 767 | foreach ( $weights as $weight ) 768 | assert ( is_int($weight) ); 769 | 770 | $this->_weights = $weights; 771 | } 772 | 773 | /// bind per-field weights by name 774 | function SetFieldWeights ( $weights ) 775 | { 776 | assert ( is_array($weights) ); 777 | foreach ( $weights as $name=>$weight ) 778 | { 779 | assert ( is_string($name) ); 780 | assert ( is_int($weight) ); 781 | } 782 | $this->_fieldweights = $weights; 783 | } 784 | 785 | /// bind per-index weights by name 786 | function SetIndexWeights ( $weights ) 787 | { 788 | assert ( is_array($weights) ); 789 | foreach ( $weights as $index=>$weight ) 790 | { 791 | assert ( is_string($index) ); 792 | assert ( is_int($weight) ); 793 | } 794 | $this->_indexweights = $weights; 795 | } 796 | 797 | /// set IDs range to match 798 | /// only match records if document ID is beetwen $min and $max (inclusive) 799 | function SetIDRange ( $min, $max ) 800 | { 801 | assert ( is_numeric($min) ); 802 | assert ( is_numeric($max) ); 803 | assert ( $min<=$max ); 804 | $this->_min_id = $min; 805 | $this->_max_id = $max; 806 | } 807 | 808 | /// set values set filter 809 | /// only match records where $attribute value is in given set 810 | function SetFilter ( $attribute, $values, $exclude=false ) 811 | { 812 | assert ( is_string($attribute) ); 813 | assert ( is_array($values) ); 814 | assert ( count($values) ); 815 | 816 | if ( is_array($values) && count($values) ) 817 | { 818 | foreach ( $values as $value ) 819 | assert ( is_numeric($value) ); 820 | 821 | $this->_filters[] = array ( "type"=>SPH_FILTER_VALUES, "attr"=>$attribute, "exclude"=>$exclude, "values"=>$values ); 822 | } 823 | } 824 | 825 | /// set range filter 826 | /// only match records if $attribute value is beetwen $min and $max (inclusive) 827 | function SetFilterRange ( $attribute, $min, $max, $exclude=false ) 828 | { 829 | assert ( is_string($attribute) ); 830 | assert ( is_numeric($min) ); 831 | assert ( is_numeric($max) ); 832 | assert ( $min<=$max ); 833 | 834 | $this->_filters[] = array ( "type"=>SPH_FILTER_RANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max ); 835 | } 836 | 837 | /// set float range filter 838 | /// only match records if $attribute value is beetwen $min and $max (inclusive) 839 | function SetFilterFloatRange ( $attribute, $min, $max, $exclude=false ) 840 | { 841 | assert ( is_string($attribute) ); 842 | assert ( is_float($min) ); 843 | assert ( is_float($max) ); 844 | assert ( $min<=$max ); 845 | 846 | $this->_filters[] = array ( "type"=>SPH_FILTER_FLOATRANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max ); 847 | } 848 | 849 | /// setup anchor point for geosphere distance calculations 850 | /// required to use @geodist in filters and sorting 851 | /// latitude and longitude must be in radians 852 | function SetGeoAnchor ( $attrlat, $attrlong, $lat, $long ) 853 | { 854 | assert ( is_string($attrlat) ); 855 | assert ( is_string($attrlong) ); 856 | assert ( is_float($lat) ); 857 | assert ( is_float($long) ); 858 | 859 | $this->_anchor = array ( "attrlat"=>$attrlat, "attrlong"=>$attrlong, "lat"=>$lat, "long"=>$long ); 860 | } 861 | 862 | /// set grouping attribute and function 863 | function SetGroupBy ( $attribute, $func, $groupsort="@group desc" ) 864 | { 865 | assert ( is_string($attribute) ); 866 | assert ( is_string($groupsort) ); 867 | assert ( $func==SPH_GROUPBY_DAY 868 | || $func==SPH_GROUPBY_WEEK 869 | || $func==SPH_GROUPBY_MONTH 870 | || $func==SPH_GROUPBY_YEAR 871 | || $func==SPH_GROUPBY_ATTR 872 | || $func==SPH_GROUPBY_ATTRPAIR ); 873 | 874 | $this->_groupby = $attribute; 875 | $this->_groupfunc = $func; 876 | $this->_groupsort = $groupsort; 877 | } 878 | 879 | /// set count-distinct attribute for group-by queries 880 | function SetGroupDistinct ( $attribute ) 881 | { 882 | assert ( is_string($attribute) ); 883 | $this->_groupdistinct = $attribute; 884 | } 885 | 886 | /// set distributed retries count and delay 887 | function SetRetries ( $count, $delay=0 ) 888 | { 889 | assert ( is_int($count) && $count>=0 ); 890 | assert ( is_int($delay) && $delay>=0 ); 891 | $this->_retrycount = $count; 892 | $this->_retrydelay = $delay; 893 | } 894 | 895 | /// set result set format (hash or array; hash by default) 896 | /// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs 897 | function SetArrayResult ( $arrayresult ) 898 | { 899 | assert ( is_bool($arrayresult) ); 900 | $this->_arrayresult = $arrayresult; 901 | } 902 | 903 | /// set attribute values override 904 | /// there can be only one override per attribute 905 | /// $values must be a hash that maps document IDs to attribute values 906 | function SetOverride ( $attrname, $attrtype, $values ) 907 | { 908 | assert ( is_string ( $attrname ) ); 909 | assert ( in_array ( $attrtype, array ( SPH_ATTR_INTEGER, SPH_ATTR_TIMESTAMP, SPH_ATTR_BOOL, SPH_ATTR_FLOAT, SPH_ATTR_BIGINT ) ) ); 910 | assert ( is_array ( $values ) ); 911 | 912 | $this->_overrides[$attrname] = array ( "attr"=>$attrname, "type"=>$attrtype, "values"=>$values ); 913 | } 914 | 915 | /// set select-list (attributes or expressions), SQL-like syntax 916 | function SetSelect ( $select ) 917 | { 918 | assert ( is_string ( $select ) ); 919 | $this->_select = $select; 920 | } 921 | 922 | ////////////////////////////////////////////////////////////////////////////// 923 | 924 | /// clear all filters (for multi-queries) 925 | function ResetFilters () 926 | { 927 | $this->_filters = array(); 928 | $this->_anchor = array(); 929 | } 930 | 931 | /// clear groupby settings (for multi-queries) 932 | function ResetGroupBy () 933 | { 934 | $this->_groupby = ""; 935 | $this->_groupfunc = SPH_GROUPBY_DAY; 936 | $this->_groupsort = "@group desc"; 937 | $this->_groupdistinct= ""; 938 | } 939 | 940 | /// clear all attribute value overrides (for multi-queries) 941 | function ResetOverrides () 942 | { 943 | $this->_overrides = array (); 944 | } 945 | 946 | ////////////////////////////////////////////////////////////////////////////// 947 | 948 | /// connect to searchd server, run given search query through given indexes, 949 | /// and return the search results 950 | function Query ( $query, $index="*", $comment="" ) 951 | { 952 | assert ( empty($this->_reqs) ); 953 | 954 | $this->AddQuery ( $query, $index, $comment ); 955 | $results = $this->RunQueries (); 956 | $this->_reqs = array (); // just in case it failed too early 957 | 958 | if ( !is_array($results) ) 959 | return false; // probably network error; error message should be already filled 960 | 961 | $this->_error = $results[0]["error"]; 962 | $this->_warning = $results[0]["warning"]; 963 | if ( $results[0]["status"]==SEARCHD_ERROR ) 964 | return false; 965 | else 966 | return $results[0]; 967 | } 968 | 969 | /// helper to pack floats in network byte order 970 | function _PackFloat ( $f ) 971 | { 972 | $t1 = pack ( "f", $f ); // machine order 973 | list(,$t2) = unpack ( "L*", $t1 ); // int in machine order 974 | return pack ( "N", $t2 ); 975 | } 976 | 977 | /// add query to multi-query batch 978 | /// returns index into results array from RunQueries() call 979 | function AddQuery ( $query, $index="*", $comment="" ) 980 | { 981 | // mbstring workaround 982 | $this->_MBPush (); 983 | 984 | // build request 985 | $req = pack ( "NNNNN", $this->_offset, $this->_limit, $this->_mode, $this->_ranker, $this->_sort ); // mode and limits 986 | $req .= pack ( "N", strlen($this->_sortby) ) . $this->_sortby; 987 | $req .= pack ( "N", strlen($query) ) . $query; // query itself 988 | $req .= pack ( "N", count($this->_weights) ); // weights 989 | foreach ( $this->_weights as $weight ) 990 | $req .= pack ( "N", (int)$weight ); 991 | $req .= pack ( "N", strlen($index) ) . $index; // indexes 992 | $req .= pack ( "N", 1 ); // id64 range marker 993 | $req .= sphPackU64 ( $this->_min_id ) . sphPackU64 ( $this->_max_id ); // id64 range 994 | 995 | // filters 996 | $req .= pack ( "N", count($this->_filters) ); 997 | foreach ( $this->_filters as $filter ) 998 | { 999 | $req .= pack ( "N", strlen($filter["attr"]) ) . $filter["attr"]; 1000 | $req .= pack ( "N", $filter["type"] ); 1001 | switch ( $filter["type"] ) 1002 | { 1003 | case SPH_FILTER_VALUES: 1004 | $req .= pack ( "N", count($filter["values"]) ); 1005 | foreach ( $filter["values"] as $value ) 1006 | $req .= sphPackI64 ( $value ); 1007 | break; 1008 | 1009 | case SPH_FILTER_RANGE: 1010 | $req .= sphPackI64 ( $filter["min"] ) . sphPackI64 ( $filter["max"] ); 1011 | break; 1012 | 1013 | case SPH_FILTER_FLOATRANGE: 1014 | $req .= $this->_PackFloat ( $filter["min"] ) . $this->_PackFloat ( $filter["max"] ); 1015 | break; 1016 | 1017 | default: 1018 | assert ( 0 && "internal error: unhandled filter type" ); 1019 | } 1020 | $req .= pack ( "N", $filter["exclude"] ); 1021 | } 1022 | 1023 | // group-by clause, max-matches count, group-sort clause, cutoff count 1024 | $req .= pack ( "NN", $this->_groupfunc, strlen($this->_groupby) ) . $this->_groupby; 1025 | $req .= pack ( "N", $this->_maxmatches ); 1026 | $req .= pack ( "N", strlen($this->_groupsort) ) . $this->_groupsort; 1027 | $req .= pack ( "NNN", $this->_cutoff, $this->_retrycount, $this->_retrydelay ); 1028 | $req .= pack ( "N", strlen($this->_groupdistinct) ) . $this->_groupdistinct; 1029 | 1030 | // anchor point 1031 | if ( empty($this->_anchor) ) 1032 | { 1033 | $req .= pack ( "N", 0 ); 1034 | } else 1035 | { 1036 | $a =& $this->_anchor; 1037 | $req .= pack ( "N", 1 ); 1038 | $req .= pack ( "N", strlen($a["attrlat"]) ) . $a["attrlat"]; 1039 | $req .= pack ( "N", strlen($a["attrlong"]) ) . $a["attrlong"]; 1040 | $req .= $this->_PackFloat ( $a["lat"] ) . $this->_PackFloat ( $a["long"] ); 1041 | } 1042 | 1043 | // per-index weights 1044 | $req .= pack ( "N", count($this->_indexweights) ); 1045 | foreach ( $this->_indexweights as $idx=>$weight ) 1046 | $req .= pack ( "N", strlen($idx) ) . $idx . pack ( "N", $weight ); 1047 | 1048 | // max query time 1049 | $req .= pack ( "N", $this->_maxquerytime ); 1050 | 1051 | // per-field weights 1052 | $req .= pack ( "N", count($this->_fieldweights) ); 1053 | foreach ( $this->_fieldweights as $field=>$weight ) 1054 | $req .= pack ( "N", strlen($field) ) . $field . pack ( "N", $weight ); 1055 | 1056 | // comment 1057 | $req .= pack ( "N", strlen($comment) ) . $comment; 1058 | 1059 | // attribute overrides 1060 | $req .= pack ( "N", count($this->_overrides) ); 1061 | foreach ( $this->_overrides as $key => $entry ) 1062 | { 1063 | $req .= pack ( "N", strlen($entry["attr"]) ) . $entry["attr"]; 1064 | $req .= pack ( "NN", $entry["type"], count($entry["values"]) ); 1065 | foreach ( $entry["values"] as $id=>$val ) 1066 | { 1067 | assert ( is_numeric($id) ); 1068 | assert ( is_numeric($val) ); 1069 | 1070 | $req .= sphPackU64 ( $id ); 1071 | switch ( $entry["type"] ) 1072 | { 1073 | case SPH_ATTR_FLOAT: $req .= $this->_PackFloat ( $val ); break; 1074 | case SPH_ATTR_BIGINT: $req .= sphPackI64 ( $val ); break; 1075 | default: $req .= pack ( "N", $val ); break; 1076 | } 1077 | } 1078 | } 1079 | 1080 | // select-list 1081 | $req .= pack ( "N", strlen($this->_select) ) . $this->_select; 1082 | 1083 | // mbstring workaround 1084 | $this->_MBPop (); 1085 | 1086 | // store request to requests array 1087 | $this->_reqs[] = $req; 1088 | return count($this->_reqs)-1; 1089 | } 1090 | 1091 | /// connect to searchd, run queries batch, and return an array of result sets 1092 | function RunQueries () 1093 | { 1094 | if ( empty($this->_reqs) ) 1095 | { 1096 | $this->_error = "no queries defined, issue AddQuery() first"; 1097 | return false; 1098 | } 1099 | 1100 | // mbstring workaround 1101 | $this->_MBPush (); 1102 | 1103 | if (!( $fp = $this->_Connect() )) 1104 | { 1105 | $this->_MBPop (); 1106 | return false; 1107 | } 1108 | 1109 | // send query, get response 1110 | $nreqs = count($this->_reqs); 1111 | $req = join ( "", $this->_reqs ); 1112 | $len = 4+strlen($req); 1113 | $req = pack ( "nnNN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, $nreqs ) . $req; // add header 1114 | 1115 | if ( !( $this->_Send ( $fp, $req, $len+8 ) ) || 1116 | !( $response = $this->_GetResponse ( $fp, VER_COMMAND_SEARCH ) ) ) 1117 | { 1118 | $this->_MBPop (); 1119 | return false; 1120 | } 1121 | 1122 | // query sent ok; we can reset reqs now 1123 | $this->_reqs = array (); 1124 | 1125 | // parse and return response 1126 | return $this->_ParseSearchResponse ( $response, $nreqs ); 1127 | } 1128 | 1129 | /// parse and return search query (or queries) response 1130 | function _ParseSearchResponse ( $response, $nreqs ) 1131 | { 1132 | $p = 0; // current position 1133 | $max = strlen($response); // max position for checks, to protect against broken responses 1134 | 1135 | $results = array (); 1136 | for ( $ires=0; $ires<$nreqs && $p<$max; $ires++ ) 1137 | { 1138 | $results[] = array(); 1139 | $result =& $results[$ires]; 1140 | 1141 | $result["error"] = ""; 1142 | $result["warning"] = ""; 1143 | 1144 | // extract status 1145 | list(,$status) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; 1146 | $result["status"] = $status; 1147 | if ( $status!=SEARCHD_OK ) 1148 | { 1149 | list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; 1150 | $message = substr ( $response, $p, $len ); $p += $len; 1151 | 1152 | if ( $status==SEARCHD_WARNING ) 1153 | { 1154 | $result["warning"] = $message; 1155 | } else 1156 | { 1157 | $result["error"] = $message; 1158 | continue; 1159 | } 1160 | } 1161 | 1162 | // read schema 1163 | $fields = array (); 1164 | $attrs = array (); 1165 | 1166 | list(,$nfields) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; 1167 | while ( $nfields-->0 && $p<$max ) 1168 | { 1169 | list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; 1170 | $fields[] = substr ( $response, $p, $len ); $p += $len; 1171 | } 1172 | $result["fields"] = $fields; 1173 | 1174 | list(,$nattrs) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; 1175 | while ( $nattrs-->0 && $p<$max ) 1176 | { 1177 | list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; 1178 | $attr = substr ( $response, $p, $len ); $p += $len; 1179 | list(,$type) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; 1180 | $attrs[$attr] = $type; 1181 | } 1182 | $result["attrs"] = $attrs; 1183 | 1184 | // read match count 1185 | list(,$count) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; 1186 | list(,$id64) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; 1187 | 1188 | // read matches 1189 | $idx = -1; 1190 | while ( $count-->0 && $p<$max ) 1191 | { 1192 | // index into result array 1193 | $idx++; 1194 | 1195 | // parse document id and weight 1196 | if ( $id64 ) 1197 | { 1198 | $doc = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8; 1199 | list(,$weight) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; 1200 | } 1201 | else 1202 | { 1203 | list ( $doc, $weight ) = array_values ( unpack ( "N*N*", 1204 | substr ( $response, $p, 8 ) ) ); 1205 | $p += 8; 1206 | $doc = sphFixUint($doc); 1207 | } 1208 | $weight = sprintf ( "%u", $weight ); 1209 | 1210 | // create match entry 1211 | if ( $this->_arrayresult ) 1212 | $result["matches"][$idx] = array ( "id"=>$doc, "weight"=>$weight ); 1213 | else 1214 | $result["matches"][$doc]["weight"] = $weight; 1215 | 1216 | // parse and create attributes 1217 | $attrvals = array (); 1218 | foreach ( $attrs as $attr=>$type ) 1219 | { 1220 | // handle 64bit ints 1221 | if ( $type==SPH_ATTR_BIGINT ) 1222 | { 1223 | $attrvals[$attr] = sphUnpackI64 ( substr ( $response, $p, 8 ) ); $p += 8; 1224 | continue; 1225 | } 1226 | 1227 | // handle floats 1228 | if ( $type==SPH_ATTR_FLOAT ) 1229 | { 1230 | list(,$uval) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; 1231 | list(,$fval) = unpack ( "f*", pack ( "L", $uval ) ); 1232 | $attrvals[$attr] = $fval; 1233 | continue; 1234 | } 1235 | 1236 | // handle everything else as unsigned ints 1237 | list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; 1238 | if ( $type & SPH_ATTR_MULTI ) 1239 | { 1240 | $attrvals[$attr] = array (); 1241 | $nvalues = $val; 1242 | while ( $nvalues-->0 && $p<$max ) 1243 | { 1244 | list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; 1245 | $attrvals[$attr][] = sphFixUint($val); 1246 | } 1247 | } else if ( $type==SPH_ATTR_STRING ) 1248 | { 1249 | $attrvals[$attr] = substr ( $response, $p, $val ); 1250 | $p += $val; 1251 | } else 1252 | { 1253 | $attrvals[$attr] = sphFixUint($val); 1254 | } 1255 | } 1256 | 1257 | if ( $this->_arrayresult ) 1258 | $result["matches"][$idx]["attrs"] = $attrvals; 1259 | else 1260 | $result["matches"][$doc]["attrs"] = $attrvals; 1261 | } 1262 | 1263 | list ( $total, $total_found, $msecs, $words ) = 1264 | array_values ( unpack ( "N*N*N*N*", substr ( $response, $p, 16 ) ) ); 1265 | $result["total"] = sprintf ( "%u", $total ); 1266 | $result["total_found"] = sprintf ( "%u", $total_found ); 1267 | $result["time"] = sprintf ( "%.3f", $msecs/1000 ); 1268 | $p += 16; 1269 | 1270 | while ( $words-->0 && $p<$max ) 1271 | { 1272 | list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; 1273 | $word = substr ( $response, $p, $len ); $p += $len; 1274 | list ( $docs, $hits ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8; 1275 | $result["words"][$word] = array ( 1276 | "docs"=>sprintf ( "%u", $docs ), 1277 | "hits"=>sprintf ( "%u", $hits ) ); 1278 | } 1279 | } 1280 | 1281 | $this->_MBPop (); 1282 | return $results; 1283 | } 1284 | 1285 | ///////////////////////////////////////////////////////////////////////////// 1286 | // excerpts generation 1287 | ///////////////////////////////////////////////////////////////////////////// 1288 | 1289 | /// connect to searchd server, and generate exceprts (snippets) 1290 | /// of given documents for given query. returns false on failure, 1291 | /// an array of snippets on success 1292 | function BuildExcerpts ( $docs, $index, $words, $opts=array() ) 1293 | { 1294 | assert ( is_array($docs) ); 1295 | assert ( is_string($index) ); 1296 | assert ( is_string($words) ); 1297 | assert ( is_array($opts) ); 1298 | 1299 | $this->_MBPush (); 1300 | 1301 | if (!( $fp = $this->_Connect() )) 1302 | { 1303 | $this->_MBPop(); 1304 | return false; 1305 | } 1306 | 1307 | ///////////////// 1308 | // fixup options 1309 | ///////////////// 1310 | 1311 | if ( !isset($opts["before_match"]) ) $opts["before_match"] = "<b>"; 1312 | if ( !isset($opts["after_match"]) ) $opts["after_match"] = "</b>"; 1313 | if ( !isset($opts["chunk_separator"]) ) $opts["chunk_separator"] = " ... "; 1314 | if ( !isset($opts["limit"]) ) $opts["limit"] = 256; 1315 | if ( !isset($opts["limit_passages"]) ) $opts["limit_passages"] = 0; 1316 | if ( !isset($opts["limit_words"]) ) $opts["limit_words"] = 0; 1317 | if ( !isset($opts["around"]) ) $opts["around"] = 5; 1318 | if ( !isset($opts["exact_phrase"]) ) $opts["exact_phrase"] = false; 1319 | if ( !isset($opts["single_passage"]) ) $opts["single_passage"] = false; 1320 | if ( !isset($opts["use_boundaries"]) ) $opts["use_boundaries"] = false; 1321 | if ( !isset($opts["weight_order"]) ) $opts["weight_order"] = false; 1322 | if ( !isset($opts["query_mode"]) ) $opts["query_mode"] = false; 1323 | if ( !isset($opts["force_all_words"]) ) $opts["force_all_words"] = false; 1324 | if ( !isset($opts["start_passage_id"]) ) $opts["start_passage_id"] = 1; 1325 | if ( !isset($opts["load_files"]) ) $opts["load_files"] = false; 1326 | if ( !isset($opts["html_strip_mode"]) ) $opts["html_strip_mode"] = "index"; 1327 | if ( !isset($opts["allow_empty"]) ) $opts["allow_empty"] = false; 1328 | if ( !isset($opts["passage_boundary"]) ) $opts["passage_boundary"] = "none"; 1329 | if ( !isset($opts["emit_zones"]) ) $opts["emit_zones"] = false; 1330 | 1331 | ///////////////// 1332 | // build request 1333 | ///////////////// 1334 | 1335 | // v.1.2 req 1336 | $flags = 1; // remove spaces 1337 | if ( $opts["exact_phrase"] ) $flags |= 2; 1338 | if ( $opts["single_passage"] ) $flags |= 4; 1339 | if ( $opts["use_boundaries"] ) $flags |= 8; 1340 | if ( $opts["weight_order"] ) $flags |= 16; 1341 | if ( $opts["query_mode"] ) $flags |= 32; 1342 | if ( $opts["force_all_words"] ) $flags |= 64; 1343 | if ( $opts["load_files"] ) $flags |= 128; 1344 | if ( $opts["allow_empty"] ) $flags |= 256; 1345 | if ( $opts["emit_zones"] ) $flags |= 512; 1346 | $req = pack ( "NN", 0, $flags ); // mode=0, flags=$flags 1347 | $req .= pack ( "N", strlen($index) ) . $index; // req index 1348 | $req .= pack ( "N", strlen($words) ) . $words; // req words 1349 | 1350 | // options 1351 | $req .= pack ( "N", strlen($opts["before_match"]) ) . $opts["before_match"]; 1352 | $req .= pack ( "N", strlen($opts["after_match"]) ) . $opts["after_match"]; 1353 | $req .= pack ( "N", strlen($opts["chunk_separator"]) ) . $opts["chunk_separator"]; 1354 | $req .= pack ( "NN", (int)$opts["limit"], (int)$opts["around"] ); 1355 | $req .= pack ( "NNN", (int)$opts["limit_passages"], (int)$opts["limit_words"], (int)$opts["start_passage_id"] ); // v.1.2 1356 | $req .= pack ( "N", strlen($opts["html_strip_mode"]) ) . $opts["html_strip_mode"]; 1357 | $req .= pack ( "N", strlen($opts["passage_boundary"]) ) . $opts["passage_boundary"]; 1358 | 1359 | // documents 1360 | $req .= pack ( "N", count($docs) ); 1361 | foreach ( $docs as $doc ) 1362 | { 1363 | assert ( is_string($doc) ); 1364 | $req .= pack ( "N", strlen($doc) ) . $doc; 1365 | } 1366 | 1367 | //////////////////////////// 1368 | // send query, get response 1369 | //////////////////////////// 1370 | 1371 | $len = strlen($req); 1372 | $req = pack ( "nnN", SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len ) . $req; // add header 1373 | if ( !( $this->_Send ( $fp, $req, $len+8 ) ) || 1374 | !( $response = $this->_GetResponse ( $fp, VER_COMMAND_EXCERPT ) ) ) 1375 | { 1376 | $this->_MBPop (); 1377 | return false; 1378 | } 1379 | 1380 | ////////////////// 1381 | // parse response 1382 | ////////////////// 1383 | 1384 | $pos = 0; 1385 | $res = array (); 1386 | $rlen = strlen($response); 1387 | for ( $i=0; $i<count($docs); $i++ ) 1388 | { 1389 | list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); 1390 | $pos += 4; 1391 | 1392 | if ( $pos+$len > $rlen ) 1393 | { 1394 | $this->_error = "incomplete reply"; 1395 | $this->_MBPop (); 1396 | return false; 1397 | } 1398 | $res[] = $len ? substr ( $response, $pos, $len ) : ""; 1399 | $pos += $len; 1400 | } 1401 | 1402 | $this->_MBPop (); 1403 | return $res; 1404 | } 1405 | 1406 | 1407 | ///////////////////////////////////////////////////////////////////////////// 1408 | // keyword generation 1409 | ///////////////////////////////////////////////////////////////////////////// 1410 | 1411 | /// connect to searchd server, and generate keyword list for a given query 1412 | /// returns false on failure, 1413 | /// an array of words on success 1414 | function BuildKeywords ( $query, $index, $hits ) 1415 | { 1416 | assert ( is_string($query) ); 1417 | assert ( is_string($index) ); 1418 | assert ( is_bool($hits) ); 1419 | 1420 | $this->_MBPush (); 1421 | 1422 | if (!( $fp = $this->_Connect() )) 1423 | { 1424 | $this->_MBPop(); 1425 | return false; 1426 | } 1427 | 1428 | ///////////////// 1429 | // build request 1430 | ///////////////// 1431 | 1432 | // v.1.0 req 1433 | $req = pack ( "N", strlen($query) ) . $query; // req query 1434 | $req .= pack ( "N", strlen($index) ) . $index; // req index 1435 | $req .= pack ( "N", (int)$hits ); 1436 | 1437 | //////////////////////////// 1438 | // send query, get response 1439 | //////////////////////////// 1440 | 1441 | $len = strlen($req); 1442 | $req = pack ( "nnN", SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len ) . $req; // add header 1443 | if ( !( $this->_Send ( $fp, $req, $len+8 ) ) || 1444 | !( $response = $this->_GetResponse ( $fp, VER_COMMAND_KEYWORDS ) ) ) 1445 | { 1446 | $this->_MBPop (); 1447 | return false; 1448 | } 1449 | 1450 | ////////////////// 1451 | // parse response 1452 | ////////////////// 1453 | 1454 | $pos = 0; 1455 | $res = array (); 1456 | $rlen = strlen($response); 1457 | list(,$nwords) = unpack ( "N*", substr ( $response, $pos, 4 ) ); 1458 | $pos += 4; 1459 | for ( $i=0; $i<$nwords; $i++ ) 1460 | { 1461 | list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4; 1462 | $tokenized = $len ? substr ( $response, $pos, $len ) : ""; 1463 | $pos += $len; 1464 | 1465 | list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4; 1466 | $normalized = $len ? substr ( $response, $pos, $len ) : ""; 1467 | $pos += $len; 1468 | 1469 | $res[] = array ( "tokenized"=>$tokenized, "normalized"=>$normalized ); 1470 | 1471 | if ( $hits ) 1472 | { 1473 | list($ndocs,$nhits) = array_values ( unpack ( "N*N*", substr ( $response, $pos, 8 ) ) ); 1474 | $pos += 8; 1475 | $res [$i]["docs"] = $ndocs; 1476 | $res [$i]["hits"] = $nhits; 1477 | } 1478 | 1479 | if ( $pos > $rlen ) 1480 | { 1481 | $this->_error = "incomplete reply"; 1482 | $this->_MBPop (); 1483 | return false; 1484 | } 1485 | } 1486 | 1487 | $this->_MBPop (); 1488 | return $res; 1489 | } 1490 | 1491 | function EscapeString ( $string ) 1492 | { 1493 | $from = array ( '\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=' ); 1494 | $to = array ( '\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=' ); 1495 | 1496 | return str_replace ( $from, $to, $string ); 1497 | } 1498 | 1499 | ///////////////////////////////////////////////////////////////////////////// 1500 | // attribute updates 1501 | ///////////////////////////////////////////////////////////////////////////// 1502 | 1503 | /// batch update given attributes in given rows in given indexes 1504 | /// returns amount of updated documents (0 or more) on success, or -1 on failure 1505 | function UpdateAttributes ( $index, $attrs, $values, $mva=false ) 1506 | { 1507 | // verify everything 1508 | assert ( is_string($index) ); 1509 | assert ( is_bool($mva) ); 1510 | 1511 | assert ( is_array($attrs) ); 1512 | foreach ( $attrs as $attr ) 1513 | assert ( is_string($attr) ); 1514 | 1515 | assert ( is_array($values) ); 1516 | foreach ( $values as $id=>$entry ) 1517 | { 1518 | assert ( is_numeric($id) ); 1519 | assert ( is_array($entry) ); 1520 | assert ( count($entry)==count($attrs) ); 1521 | foreach ( $entry as $v ) 1522 | { 1523 | if ( $mva ) 1524 | { 1525 | assert ( is_array($v) ); 1526 | foreach ( $v as $vv ) 1527 | assert ( is_int($vv) ); 1528 | } else 1529 | assert ( is_int($v) ); 1530 | } 1531 | } 1532 | 1533 | // build request 1534 | $this->_MBPush (); 1535 | $req = pack ( "N", strlen($index) ) . $index; 1536 | 1537 | $req .= pack ( "N", count($attrs) ); 1538 | foreach ( $attrs as $attr ) 1539 | { 1540 | $req .= pack ( "N", strlen($attr) ) . $attr; 1541 | $req .= pack ( "N", $mva ? 1 : 0 ); 1542 | } 1543 | 1544 | $req .= pack ( "N", count($values) ); 1545 | foreach ( $values as $id=>$entry ) 1546 | { 1547 | $req .= sphPackU64 ( $id ); 1548 | foreach ( $entry as $v ) 1549 | { 1550 | $req .= pack ( "N", $mva ? count($v) : $v ); 1551 | if ( $mva ) 1552 | foreach ( $v as $vv ) 1553 | $req .= pack ( "N", $vv ); 1554 | } 1555 | } 1556 | 1557 | // connect, send query, get response 1558 | if (!( $fp = $this->_Connect() )) 1559 | { 1560 | $this->_MBPop (); 1561 | return -1; 1562 | } 1563 | 1564 | $len = strlen($req); 1565 | $req = pack ( "nnN", SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len ) . $req; // add header 1566 | if ( !$this->_Send ( $fp, $req, $len+8 ) ) 1567 | { 1568 | $this->_MBPop (); 1569 | return -1; 1570 | } 1571 | 1572 | if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_UPDATE ) )) 1573 | { 1574 | $this->_MBPop (); 1575 | return -1; 1576 | } 1577 | 1578 | // parse response 1579 | list(,$updated) = unpack ( "N*", substr ( $response, 0, 4 ) ); 1580 | $this->_MBPop (); 1581 | return $updated; 1582 | } 1583 | 1584 | ///////////////////////////////////////////////////////////////////////////// 1585 | // persistent connections 1586 | ///////////////////////////////////////////////////////////////////////////// 1587 | 1588 | function Open() 1589 | { 1590 | if ( $this->_socket !== false ) 1591 | { 1592 | $this->_error = 'already connected'; 1593 | return false; 1594 | } 1595 | if ( !$fp = $this->_Connect() ) 1596 | return false; 1597 | 1598 | // command, command version = 0, body length = 4, body = 1 1599 | $req = pack ( "nnNN", SEARCHD_COMMAND_PERSIST, 0, 4, 1 ); 1600 | if ( !$this->_Send ( $fp, $req, 12 ) ) 1601 | return false; 1602 | 1603 | $this->_socket = $fp; 1604 | return true; 1605 | } 1606 | 1607 | function Close() 1608 | { 1609 | if ( $this->_socket === false ) 1610 | { 1611 | $this->_error = 'not connected'; 1612 | return false; 1613 | } 1614 | 1615 | fclose ( $this->_socket ); 1616 | $this->_socket = false; 1617 | 1618 | return true; 1619 | } 1620 | 1621 | ////////////////////////////////////////////////////////////////////////// 1622 | // status 1623 | ////////////////////////////////////////////////////////////////////////// 1624 | 1625 | function Status () 1626 | { 1627 | $this->_MBPush (); 1628 | if (!( $fp = $this->_Connect() )) 1629 | { 1630 | $this->_MBPop(); 1631 | return false; 1632 | } 1633 | 1634 | $req = pack ( "nnNN", SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, 1 ); // len=4, body=1 1635 | if ( !( $this->_Send ( $fp, $req, 12 ) ) || 1636 | !( $response = $this->_GetResponse ( $fp, VER_COMMAND_STATUS ) ) ) 1637 | { 1638 | $this->_MBPop (); 1639 | return false; 1640 | } 1641 | 1642 | $res = substr ( $response, 4 ); // just ignore length, error handling, etc 1643 | $p = 0; 1644 | list ( $rows, $cols ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8; 1645 | 1646 | $res = array(); 1647 | for ( $i=0; $i<$rows; $i++ ) 1648 | for ( $j=0; $j<$cols; $j++ ) 1649 | { 1650 | list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; 1651 | $res[$i][] = substr ( $response, $p, $len ); $p += $len; 1652 | } 1653 | 1654 | $this->_MBPop (); 1655 | return $res; 1656 | } 1657 | 1658 | ////////////////////////////////////////////////////////////////////////// 1659 | // flush 1660 | ////////////////////////////////////////////////////////////////////////// 1661 | 1662 | function FlushAttributes () 1663 | { 1664 | $this->_MBPush (); 1665 | if (!( $fp = $this->_Connect() )) 1666 | { 1667 | $this->_MBPop(); 1668 | return -1; 1669 | } 1670 | 1671 | $req = pack ( "nnN", SEARCHD_COMMAND_FLUSHATTRS, VER_COMMAND_FLUSHATTRS, 0 ); // len=0 1672 | if ( !( $this->_Send ( $fp, $req, 8 ) ) || 1673 | !( $response = $this->_GetResponse ( $fp, VER_COMMAND_FLUSHATTRS ) ) ) 1674 | { 1675 | $this->_MBPop (); 1676 | return -1; 1677 | } 1678 | 1679 | $tag = -1; 1680 | if ( strlen($response)==4 ) 1681 | list(,$tag) = unpack ( "N*", $response ); 1682 | else 1683 | $this->_error = "unexpected response length"; 1684 | 1685 | $this->_MBPop (); 1686 | return $tag; 1687 | } 1688 | } 1689 | 1690 | // 1691 | // $Id$ 1692 | // 1693 | -------------------------------------------------------------------------------- /test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asphxg/findpass/5a6708bda5be5cac1837e10f7a072ed3d9e2f03d/test.jpg --------------------------------------------------------------------------------